Build: Run GitHub Action browser tests on Playwright WebKit

So far, we've been running browser tests on GitHub Actions in Chrome
and Firefox. Regular Safari is not available in GitHub Actions but
Playwright WebKit comes close to a dev version of Safari.

With this change, our GitHub CI & local test runs will invoke tests on
all actively developed browser engines on all PRs.

Also, our GitHub Actions browser tests are now running on Node.js 18.

Detection of the Playwright WebKit browser in support unit tests is done
by checking if the `test_browser` query parameter is set to `"Playwright"`;
this is a `karma-webkit-launcher` feature. Detecting that browser via
user agent as we normally do is hard as the UA on Linux is very similar
to a real Safari one but it actually uses a newer version of the engine.

In addition, we now allow to pass custom browsers when one needs it;
e.g., to run the tests in all three engines on Linux/macOS, run:
```
grunt && BROWSERS=ChromeHeadless,FirefoxHeadless,WebkitHeadless grunt karma:main
```

Closes gh-5190
This commit is contained in:
Michał Gołębiowski-Owczarek 2023-01-23 23:49:44 +01:00 committed by GitHub
parent ce90a48450
commit b02a257f98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 38 deletions

View File

@ -16,32 +16,32 @@ jobs:
NODE_VERSION: [10.x, 14.x, 16.x, 18.x, 19.x] NODE_VERSION: [10.x, 14.x, 16.x, 18.x, 19.x]
NPM_SCRIPT: ["test:browserless"] NPM_SCRIPT: ["test:browserless"]
include: include:
- NAME: "Browser tests: full build, Chrome & Firefox stable" - NAME: "Browser tests: full build, Chrome, Firefox & WebKit"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:browser" NPM_SCRIPT: "test:browser"
BROWSERS: "ChromeHeadless,FirefoxHeadless" BROWSERS: "ChromeHeadless,FirefoxHeadless,WebkitHeadless"
- NAME: "Browser tests: slim build, Chrome stable" - NAME: "Browser tests: slim build, Chrome"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:slim" NPM_SCRIPT: "test:slim"
BROWSERS: "ChromeHeadless" BROWSERS: "ChromeHeadless"
- NAME: "Browser tests: no-deprecated build, Chrome stable" - NAME: "Browser tests: no-deprecated build, Chrome"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:no-deprecated" NPM_SCRIPT: "test:no-deprecated"
BROWSERS: "ChromeHeadless" BROWSERS: "ChromeHeadless"
- NAME: "Browser tests: selector-native build, Chrome stable" - NAME: "Browser tests: selector-native build, Chrome"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:selector-native" NPM_SCRIPT: "test:selector-native"
BROWSERS: "ChromeHeadless" BROWSERS: "ChromeHeadless"
- NAME: "Browser tests: ES modules build, Chrome stable" - NAME: "Browser tests: ES modules build, Chrome"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:esmodules" NPM_SCRIPT: "test:esmodules"
BROWSERS: "ChromeHeadless" BROWSERS: "ChromeHeadless"
- NAME: "Browser tests: AMD build, Chrome stable" - NAME: "Browser tests: AMD build, Chrome"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:amd" NPM_SCRIPT: "test:amd"
BROWSERS: "ChromeHeadless" BROWSERS: "ChromeHeadless"
- NAME: "Browser tests: full build, Firefox ESR" - NAME: "Browser tests: full build, Firefox ESR"
NODE_VERSION: "16.x" NODE_VERSION: "18.x"
NPM_SCRIPT: "test:browser" NPM_SCRIPT: "test:browser"
BROWSERS: "FirefoxHeadless" BROWSERS: "FirefoxHeadless"
steps: steps:
@ -71,6 +71,10 @@ jobs:
run: | run: |
npm install npm install
- name: Install Playwright dependencies
run: npx playwright-webkit install-deps
if: "matrix.NPM_SCRIPT == 'test:browser'"
- name: Run tests - name: Run tests
env: env:
BROWSERS: ${{ matrix.BROWSERS }} BROWSERS: ${{ matrix.BROWSERS }}

View File

@ -2,8 +2,8 @@
module.exports = function( grunt ) { module.exports = function( grunt ) {
function readOptionalJSON( filepath ) { function readOptionalJSON( filepath ) {
var stripJSONComments = require( "strip-json-comments" ), const stripJSONComments = require( "strip-json-comments" );
data = {}; let data = {};
try { try {
data = JSON.parse( stripJSONComments( data = JSON.parse( stripJSONComments(
fs.readFileSync( filepath, { encoding: "utf8" } ) fs.readFileSync( filepath, { encoding: "utf8" } )
@ -12,19 +12,23 @@ module.exports = function( grunt ) {
return data; return data;
} }
// Support: Node.js <12 const fs = require( "fs" );
// Skip running tasks that dropped support for Node.js 10 const gzip = require( "gzip-js" );
const nodeV14OrNewer = !/^v1[0-3]\./.test( process.version );
const nodeV17OrNewer = !/^v1[0-6]\./.test( process.version );
const customBrowsers = process.env.BROWSERS && process.env.BROWSERS.split( "," );
// Support: Node.js <14
// Skip running tasks that dropped support for Node.js 10 or 12
// in this Node version. // in this Node version.
function runIfNewNode( task ) { function runIfNewNode( task ) {
return oldNode ? "print_old_node_message:" + task : task; return nodeV14OrNewer ? task : "print_old_node_message:" + task;
} }
var fs = require( "fs" ), if ( nodeV14OrNewer ) {
gzip = require( "gzip-js" ), const playwright = require( "playwright-webkit" );
oldNode = /^v10\./.test( process.version ), process.env.WEBKIT_HEADLESS_BIN = playwright.webkit.executablePath();
nodeV17OrNewer = !/^v1[0246]\./.test( process.version ), }
isCi = process.env.GITHUB_ACTION,
ciBrowsers = process.env.BROWSERS && process.env.BROWSERS.split( "," );
if ( !grunt.option( "filename" ) ) { if ( !grunt.option( "filename" ) ) {
grunt.option( "filename", "jquery.js" ); grunt.option( "filename", "jquery.js" );
@ -242,10 +246,11 @@ module.exports = function( grunt ) {
singleRun: true singleRun: true
}, },
main: { main: {
browsers: isCi && ciBrowsers || [ "ChromeHeadless", "FirefoxHeadless" ] browsers: customBrowsers ||
[ "ChromeHeadless", "FirefoxHeadless", "WebkitHeadless" ]
}, },
esmodules: { esmodules: {
browsers: isCi && ciBrowsers || [ "ChromeHeadless" ], browsers: customBrowsers || [ "ChromeHeadless" ],
options: { options: {
client: { client: {
qunit: { qunit: {
@ -260,7 +265,7 @@ module.exports = function( grunt ) {
} }
}, },
amd: { amd: {
browsers: isCi && ciBrowsers || [ "ChromeHeadless" ], browsers: customBrowsers || [ "ChromeHeadless" ],
options: { options: {
client: { client: {
qunit: { qunit: {
@ -352,7 +357,7 @@ module.exports = function( grunt ) {
// Load grunt tasks from NPM packages // Load grunt tasks from NPM packages
require( "load-grunt-tasks" )( grunt, { require( "load-grunt-tasks" )( grunt, {
pattern: oldNode ? [ "grunt-*", "!grunt-eslint" ] : [ "grunt-*" ] pattern: nodeV14OrNewer ? [ "grunt-*" ] : [ "grunt-*", "!grunt-eslint" ]
} ); } );
// Integrate jQuery specific tasks // Integrate jQuery specific tasks

View File

@ -23,7 +23,6 @@
"url": "https://github.com/jquery/jquery/issues" "url": "https://github.com/jquery/jquery/issues"
}, },
"license": "MIT", "license": "MIT",
"dependencies": {},
"devDependencies": { "devDependencies": {
"@babel/core": "7.10.5", "@babel/core": "7.10.5",
"@babel/plugin-transform-for-of": "7.10.4", "@babel/plugin-transform-for-of": "7.10.4",
@ -47,15 +46,17 @@
"gzip-js": "0.3.2", "gzip-js": "0.3.2",
"husky": "4.2.5", "husky": "4.2.5",
"jsdom": "19.0.0", "jsdom": "19.0.0",
"karma": "^6.3.17", "karma": "6.4.1",
"karma-browserstack-launcher": "1.6.0", "karma-browserstack-launcher": "1.6.0",
"karma-chrome-launcher": "3.1.1", "karma-chrome-launcher": "3.1.1",
"karma-firefox-launcher": "2.1.2", "karma-firefox-launcher": "2.1.2",
"karma-ie-launcher": "1.0.0", "karma-ie-launcher": "1.0.0",
"karma-jsdom-launcher": "12.0.0", "karma-jsdom-launcher": "12.0.0",
"karma-qunit": "4.1.2", "karma-qunit": "4.1.2",
"karma-webkit-launcher": "2.1.0",
"load-grunt-tasks": "5.1.0", "load-grunt-tasks": "5.1.0",
"native-promise-only": "0.8.1", "native-promise-only": "0.8.1",
"playwright-webkit": "1.29.2",
"promises-aplus-tests": "2.1.2", "promises-aplus-tests": "2.1.2",
"q": "1.5.1", "q": "1.5.1",
"qunit": "2.10.1", "qunit": "2.10.1",

View File

@ -2270,7 +2270,7 @@ if ( typeof window.ArrayBuffer === "undefined" || typeof new XMLHttpRequest().re
// beforeunload, unload, pagehide, and visibilitychange event handlers. // beforeunload, unload, pagehide, and visibilitychange event handlers.
// See https://bugs.chromium.org/p/chromium/issues/detail?id=952452 // See https://bugs.chromium.org/p/chromium/issues/detail?id=952452
// Safari 13 did similar changes. The below check will catch them both. // Safari 13 did similar changes. The below check will catch them both.
if ( !/safari/i.test( navigator.userAgent ) ) { if ( !/webkit/i.test( navigator.userAgent ) ) {
testIframe( testIframe(
"trac-14379 - jQuery.ajax() on unload", "trac-14379 - jQuery.ajax() on unload",
"ajax/onunload.html", "ajax/onunload.html",

View File

@ -1757,7 +1757,7 @@ QUnit.testUnlessIE( "css(--customProperty)", function( assert ) {
var div = jQuery( "<div>" ).appendTo( "#qunit-fixture" ), var div = jQuery( "<div>" ).appendTo( "#qunit-fixture" ),
$elem = jQuery( "<div>" ).addClass( "test__customProperties" ) $elem = jQuery( "<div>" ).addClass( "test__customProperties" )
.appendTo( "#qunit-fixture" ), .appendTo( "#qunit-fixture" ),
webkitOrBlink = /\bsafari\b/i.test( navigator.userAgent ), webkitOrBlink = /\webkit\b/i.test( navigator.userAgent ),
expected = 20; expected = 20;
if ( webkitOrBlink ) { if ( webkitOrBlink ) {

View File

@ -1,7 +1,11 @@
QUnit.module( "selector", { QUnit.module( "selector", {
beforeEach: function() { beforeEach: function() {
this.safari = /\bsafari\b/i.test( navigator.userAgent ) &&
!/\b(?:headless)?chrome\b/i.test( navigator.userAgent ); // Playwright WebKit on macOS doesn't expose `Safari` in its user agent
// string; use the "AppleWebKit" token. This token is also present
// in the Chromium UA, but it is locked to an older version there.
// Modern WebKit (Safari 13+) locks it to `605.1.15`.
this.safari = /\bapplewebkit\/605\.1\.15\b/i.test( navigator.userAgent );
}, },
afterEach: moduleTeardown afterEach: moduleTeardown
} ); } );

View File

@ -70,6 +70,10 @@ testIframe(
cssSupportsSelector: false, cssSupportsSelector: false,
reliableTrDimensions: true reliableTrDimensions: true
}, },
webkit: {
cssSupportsSelector: true,
reliableTrDimensions: true
},
firefox_102: { firefox_102: {
cssSupportsSelector: false, cssSupportsSelector: false,
reliableTrDimensions: false reliableTrDimensions: false
@ -97,14 +101,26 @@ testIframe(
// Catches Edge, Chrome on Android & Opera as well. // Catches Edge, Chrome on Android & Opera as well.
expected = expectedMap.chrome; expected = expectedMap.chrome;
} else if ( /\b\d+(\.\d+)+ safari/i.test( userAgent ) ) {
expected = expectedMap.safari;
} else if ( /firefox\/102\./i.test( userAgent ) ) { } else if ( /firefox\/102\./i.test( userAgent ) ) {
expected = expectedMap.firefox_102; expected = expectedMap.firefox_102;
} else if ( /firefox/i.test( userAgent ) ) { } else if ( /firefox/i.test( userAgent ) ) {
expected = expectedMap.firefox; expected = expectedMap.firefox;
} else if ( /(?:iphone|ipad);.*(?:iphone)? os \d+_/i.test( userAgent ) ) { } else if ( /(?:iphone|ipad);.*(?:iphone)? os \d+_/i.test( userAgent ) ) {
expected = expectedMap.ios; expected = expectedMap.ios;
} else if ( typeof URLSearchParams !== "undefined" &&
// `karma-webkit-launcher` adds `test_browser=Playwright` to the query string.
// The normal way of using user agent to detect the browser won't help
// as on macOS Playwright doesn't specify the `Safari` token but on Linux
// it does.
// See https://github.com/google/karma-webkit-launcher#detected-if-safari-or-playwright-is-used
new URLSearchParams( document.referrer || window.location.search ).get(
"test_browser"
) === "Playwright"
) {
expected = expectedMap.webkit;
} else if ( /\b\d+(\.\d+)+ safari/i.test( userAgent ) ) {
expected = expectedMap.safari;
} }
QUnit.test( "Verify that support tests resolve as expected per browser", function( assert ) { QUnit.test( "Verify that support tests resolve as expected per browser", function( assert ) {
@ -134,7 +150,8 @@ testIframe(
for ( i in expected ) { for ( i in expected ) {
assert.equal( computedSupport[ i ], expected[ i ], assert.equal( computedSupport[ i ], expected[ i ],
"jQuery.support['" + i + "']: " + computedSupport[ i ] + "jQuery.support['" + i + "']: " + computedSupport[ i ] +
", expected['" + i + "']: " + expected[ i ] ); ", expected['" + i + "']: " + expected[ i ] +
";\nUser Agent: " + navigator.userAgent );
} }
} ); } );