diff --git a/common/web/gesture-recognizer/build.sh b/common/web/gesture-recognizer/build.sh index 606b59f8735..a27cd3f8d16 100755 --- a/common/web/gesture-recognizer/build.sh +++ b/common/web/gesture-recognizer/build.sh @@ -33,10 +33,15 @@ builder_parse "$@" # TODO: build if out-of-date if test is specified # TODO: configure if npm has not been run, and build is specified -if builder_start_action configure; then +function do_configure() { verify_npm_setup - builder_finish_action success configure -fi + + # Configure Web browser-engine testing environments. As is, this should only + # make changes when we update the dependency, even on our CI build agents. + playwright install +} + +builder_run_action configure do_configure if builder_start_action clean; then rm -rf build/ diff --git a/common/web/gesture-recognizer/package.json b/common/web/gesture-recognizer/package.json index ae36c51eaa7..7bd7525a677 100644 --- a/common/web/gesture-recognizer/package.json +++ b/common/web/gesture-recognizer/package.json @@ -3,20 +3,6 @@ "description": "The core gesture-recognition engine used by Keyman's Web-based OSKs.", "devDependencies": { "@keymanapp/resources-gosh": "*", - "@types/sinon": "^10.0.13", - "karma": "^6.4.1", - "karma-browserstack-launcher": "^1.6.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-firefox-launcher": "^1.1.0", - "karma-fixture": "^0.2.6", - "karma-html2js-preprocessor": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-fixtures-preprocessor": "0.0.6", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-teamcity-reporter": "^1.1.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "promise-status-async": "^1.2.10", diff --git a/common/web/gesture-recognizer/src/test/auto/browser/CI.conf.cjs b/common/web/gesture-recognizer/src/test/auto/browser/CI.conf.cjs deleted file mode 100644 index 882f83cb2f0..00000000000 --- a/common/web/gesture-recognizer/src/test/auto/browser/CI.conf.cjs +++ /dev/null @@ -1,4 +0,0 @@ -var BASE_CONFIG = require("./base.conf.cjs"); -var ci_config_adapter = require("../../../../../../../common/test/resources/karma-browserstack-config.cjs"); - -module.exports = ci_config_adapter(BASE_CONFIG, "partial"); \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/base.conf.cjs b/common/web/gesture-recognizer/src/test/auto/browser/base.conf.cjs deleted file mode 100644 index bcc60f3f522..00000000000 --- a/common/web/gesture-recognizer/src/test/auto/browser/base.conf.cjs +++ /dev/null @@ -1,97 +0,0 @@ -// Karma configuration -// Generated on Mon Jan 29 2018 11:58:53 GMT+0700 (SE Asia Standard Time) - -// Super-useful! http://www.bradoncode.com/blog/2015/02/27/karma-tutorial/#handling-html-fixtures - -module.exports = { - // base path that will be used to resolve all patterns (eg. files, exclude), - // set to the base folder of the repository. - basePath: '../../../../../../..', - - // Safeguards against disconnecting after starting tests. - browserNoActivityTimeout: 60000, - - client: { - /* `client.args` here is passed to the test runner page as `__karma__.config.args`. - * - * Karma doc type spec says "array", so we use an array. It also gives us room to add alternate - * configuration details later if we need to, though on a CI vs local basis only. - * - * Timeouts below are in milliseconds - */ - args: [{ - type: "timeouts", // This base is designed for local machine testing. - eventDelay: 60, // Designed for small delays to allow time for event handling to occur before proceeding. - // Make sure this stays under 1/4 of 'standard', as multiple eventDelays may occur within a test. - standard: 5000, - scriptLoad: 8000, - uiLoad: 30000, // Loads two scripts + includes internal setup/timeout time requirements. - // At this time of writing this, UI script loading is one of the longest checks. - }] - }, - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'fixture'], - - // list of files / patterns to load in the browser - files: [ - { pattern: 'common/web/gesture-recognizer/build/lib/index.mjs', type: 'module' }, // The primary gesture-recognizer script. - { pattern: 'common/web/gesture-recognizer/build/lib/index.mjs.map', watched: true, served: true, included: false }, - - { pattern: 'common/web/gesture-recognizer/build/tools/lib/index.mjs', type: 'module' }, // The primary unit-test-resources script. - { pattern: 'common/web/gesture-recognizer/build/tools/lib/index.js.map', watched: true, served: true, included: false }, - - { pattern: 'node_modules/chai/chai.js', watched: true, served: true, included: false, type: 'module'}, - 'node_modules/sinon/pkg/sinon.js', - - 'common/test/resources/timeout-adapter.js', // Handles timeout configuration for local vs BrowserStack-based testing - - { pattern: 'common/web/gesture-recognizer/src/test/auto/browser/cases/**/*.js', type: 'module' }, // Where the tests actually reside. - - 'common/web/gesture-recognizer/build/tools/host-fixture.html', // The primary test fixture's build output location - 'common/web/gesture-recognizer/build/tools/gestureHost.css', // The primary test fixture's backing CSS stylesheet. Note: Karma will auto-link it! - 'common/web/gesture-recognizer/src/test/resources/json/**/*.json', // Where pre-loaded JSON resides. - ], - - proxies: { - "/resources/": "common/web/gesture-recognizer/src/test/resources/", - }, - - - // list of files / patterns to exclude - exclude: [ - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'common/web/gesture-recognizer/build/tools/host-fixture.html': ['html2js'], - 'common/web/gesture-recognizer/src/test/resources/fixtures/**/*.html' : ['html2js'], - 'common/web/gesture-recognizer/src/test/resources/json/**/*.json' : ['json_fixtures'] - }, - - html2JsPreprocessor: { - stripPrefix: 'common/web/gesture-recognizer/build/tools' - }, - - // Settings to properly configure how JSON fixtures are automatically loaded by Karma. - jsonFixturesPreprocessor: { - stripPrefix: 'common/web/gesture-recognizer/src/test/resources/json/', - variableName: '__json__' - }, - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - // if false, it generates the pages and acts as a persistent server. - singleRun: true, -} diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.js b/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts similarity index 75% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/canary.js rename to common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts index 65a8ece229c..a17311bec82 100644 --- a/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.js +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts @@ -1,31 +1,40 @@ -import { assert } from '../../../../../../../../node_modules/chai/chai.js'; +import { assert } from 'chai'; +import sinon from 'sinon'; import { FixtureLayoutConfiguration, HostFixtureLayoutController, InputSequenceSimulator -} from '../../../../../build/tools/lib/index.mjs'; +} from '#tools'; describe("'Canary' checks", function() { - this.timeout(testconfig.timeouts.standard); + this.timeout(5000); - before(function() { - fixture.setBase(''); - }) + let domain: string; + + before(async () => { + let loc = document.location; + // config.testFile generally starts with a '/', with the path resembling the actual full local + // filesystem for the drive. + domain = `${loc.protocol}/${loc.host}` + + // Test-config setups will take care of the rest; the server-path will be rooted at the repo root. + // With aliasing for resources/. + }); it('host-fixture.html + gestureHost.css', function() { - let element = fixture.load('host-fixture.html')[0]; + let element = document.getElementById('host-fixture'); // Ensure that not only did we get an element, we got the expected element. assert.isNotNull(element); assert.isDefined(element); - assert.equal(element.id, 'host-fixture'); // If the CSS is missing, the element will default to zero height. assert.notEqual(element.getBoundingClientRect().height, 0); }); - it('canaryRecording.json', function() { - let jsonObject = window['__json__'].canaryRecording; + it('canaryRecording.json', async function() { + const jsonResponse = await fetch(new URL(`${domain}/resources/json/canaryRecording.json`)); + const jsonObject = await jsonResponse.json(); assert.isNotNull(jsonObject); assert.isDefined(jsonObject); @@ -34,34 +43,32 @@ describe("'Canary' checks", function() { assert.equal(config.deviceStyle, 'screen4'); }); - it('Testing.HostFixtureLayoutController', function(done) { - let targetRoot = fixture.load('host-fixture.html')[0]; - let jsonObject = window['__json__'].canaryRecording; + it('Testing.HostFixtureLayoutController', async function() { + const jsonResponse = await fetch(new URL(`${domain}/resources/json/canaryRecording.json`)); + const jsonObject = await jsonResponse.json(); + const targetRoot = document.getElementById('host-fixture'); let controller = new HostFixtureLayoutController(); // Note: this is set BEFORE the controller is configured (in the following line). // The class is designed to support this. controller.layoutConfiguration = new FixtureLayoutConfiguration(jsonObject.config); - controller.connect().then(() => { - assert.isTrue(targetRoot.className.indexOf('screen4') > -1, "Could not apply configuration spec from recorded JSON!"); - done(); - }).finally(() => controller.destroy()); - }) - afterEach(function() { - fixture.cleanup(); + try { + await controller.connect(); + assert.isTrue(targetRoot.className.indexOf('screen4') > -1, "Could not apply configuration spec from recorded JSON!"); + } finally { + controller.destroy(); + } }) describe('event simulation', function() { beforeEach(function(done) { - fixture.load('host-fixture.html'); this.controller = new HostFixtureLayoutController(); this.controller.connect().then(() => done()); }); afterEach(function() { this.controller.destroy(); - fixture.cleanup(); }); it("InputSequenceSimulator.replayTouchSample", async function() { diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/gestureHost.css b/common/web/gesture-recognizer/src/test/auto/browser/cases/gestureHost.css new file mode 100644 index 00000000000..7d82132495c --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/gestureHost.css @@ -0,0 +1,216 @@ +.bounds-legend { + background-color: black; +} + +.receiver-legend { + background-color: beige; +} + +.roaming-legend { + background-color: lightblue; +} + +.shim-legend { + background-color: whitesmoke; +} + +.safe-zone-legend { + border: 1px dashed black; +} + +.safe-proximity-legend { + border: 1px dashed lightgray; +} + +#host-fixture { + max-width: fit-content; + max-height: fit-content; +} + +#host-fixture.screen4.bounds1.full { + padding: 20px 0px 0px 0px; +} + +#host-fixture.screen4.bounds2.full { + padding: 30px 10px 0px 10px; +} + +#host-fixture.bounds4.full { + padding: 0px 0px 45px 0px; +} + +#host-fixture.screen3.bounds4.full { + padding: 0px; +} + +#host-fixture.screen4.bounds4.full { + padding: 25px 0px 45px 0px; +} + +.faux-screen { + margin: auto; + max-width: fit-content; + max-height: fit-content; + background-color: black; + border: 4px solid black; + position: relative; +} + +.faux-screen-border { + border: 4px solid black; + position: absolute; + margin: -4px; + width: 100%; + height: 100%; + z-index: 100; + pointer-events: none; +} + +.safe-zone { + border: 1px dashed black; + position: absolute; + z-index: 101; + pointer-events: none; +} + +.safe-default .safe-zone { + margin: 2px; + width: calc(100% - 6px); + height: calc(100% - 6px); +} + +.safe-loose .safe-zone { + margin: 4px; + width: calc(100% - 10px); + height: calc(100% - 10px); +} + +.safe-generous .safe-zone { + margin: 16px; + width: calc(100% - 34px); + height: calc(100% - 34px); +} + +.safe-proximity { + border: 1px dashed lightgray; + position: absolute; + pointer-events: none; +} + +.safe-default .safe-proximity { + margin: 3px; + width: calc(100% - 8px); + height: calc(100% - 8px); +} + +.safe-loose .safe-proximity { + margin: 5px; + width: calc(100% - 12px); + height: calc(100% - 12px); +} + +.safe-generous .safe-proximity { + margin: 20px; + width: calc(100% - 42px); + height: calc(100% - 42px); +} + +#faux-page { + margin: auto; + background-color: white; + max-width: fit-content; + max-height: fit-content; + overflow: visible; + position: relative; + z-index: 2 +} + +.screen1 #faux-page { + padding: 100px 2px 4px 20px; +} + +.screen2 #faux-page { + padding: 180px 0px 0px 0px; +} + +.screen3 #faux-page { + padding: 120px 20px 40px 20px; +} + +.screen4 #faux-page { + padding: 0px; +} + +.receiver-host { + margin: auto; + width: 240px; + height: 180px; + position: relative; + z-index: initial; /* resets z-index stacking, allowing the child "roaming-bounds" to be placed behind it */ +} + +.main-receiver { /* is the full ".full" definition */ + user-select: none; + width: 100%; + height: 100%; + background-color: beige; + position: absolute; + top: 0px; + bottom: 0px; +} + +.popup .main-receiver { + width: 160px; + height: 45px; + right: 0px; + bottom: 90px; + top: unset; +} + +#simmed-shim { + position: absolute; + bottom: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: -2; + background-color: whitesmoke; + ; +} + +#roaming-bounds { + position: absolute; + bottom: 0px; + left: 0px; + width: 100%; + height: 100%; + z-index: -1; + background-color: lightblue; + pointer-events: none; +} + +.bounds1 #roaming-bounds { + bottom: 0px; + left: 0px; + width: 100%; + height: calc(100% + 25px); +} + +.bounds2 #roaming-bounds { + bottom: -10px; + left: -10px; + width: calc(100% + 20px); + height: calc(100% + 40px); +} + +.bounds3 #roaming-bounds { + width: 0%; + height: 0%; +} + +.bounds4 #roaming-bounds { + bottom: -45px; + left: -25px; + width: calc(100% + 50px); + height: calc(100% + 70px); +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/host-page.spec.html b/common/web/gesture-recognizer/src/test/auto/browser/cases/host-page.spec.html new file mode 100644 index 00000000000..c4a4d7a47e8 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/host-page.spec.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + Gesture Recognizer Testing Page + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+ + + + \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.js b/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts similarity index 74% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.js rename to common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts index c4a4b0bf55d..e6fb6ad4309 100644 --- a/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.js +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts @@ -1,48 +1,45 @@ -import { assert } from '../../../../../../../../node_modules/chai/chai.js'; +import { assert } from 'chai'; +import sinon from 'sinon'; import { FixtureLayoutConfiguration, HostFixtureLayoutController, InputSequenceSimulator, SequenceRecorder -} from '../../../../../build/tools/lib/index.mjs'; +} from '#tools'; describe("Layer one - DOM -> InputSequence", function() { - this.timeout(testconfig.timeouts.standard); + this.timeout(5000); - before(function() { - fixture.setBase(''); - }); + let controller: HostFixtureLayoutController; beforeEach(function(done) { - fixture.load('host-fixture.html'); - this.controller = new HostFixtureLayoutController(); - this.controller.connect().then(() => done()); + controller = new HostFixtureLayoutController(); + controller.connect().then(() => done()); }); afterEach(function() { - this.controller.destroy(); - fixture.cleanup(); + controller.destroy(); }); describe('other tests', function() { it("starts in roaming zone are ignored", function() { - let playbackEngine = new InputSequenceSimulator(this.controller); - let recorder = new SequenceRecorder(this.controller); + let playbackEngine = new InputSequenceSimulator(controller); + let recorder = new SequenceRecorder(controller); let layout = new FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); - this.controller.layoutConfiguration = layout; + controller.layoutConfiguration = layout; let fireEvent = () => { playbackEngine.replayTouchSamples(/*relative coord:*/ [{sample: {targetX: 10, targetY: -5}, identifier: 1}], /*state:*/ "start", /*recentTouches:*/ [], - /*targetElement:*/ this.controller.recognizer.config.maxRoamingBounds + /*targetElement:*/ controller.recognizer.config.maxRoamingBounds ); } // This test is invalidated if the handler itself isn't called. So... let's verify that! // This requires white-box inspection of the actual handler control-flow, and we must do - let touchEngine = this.controller.recognizer.touchEngine; + let touchEngine = controller.recognizer.touchEngine; let trueHandler = touchEngine.onTouchStart; let fakeHandler = touchEngine.onTouchStart = sinon.fake(); fireEvent(); @@ -60,10 +57,10 @@ describe("Layer one - DOM -> InputSequence", function() { }); it("ignores target-external events", function() { - let playbackEngine = new InputSequenceSimulator(this.controller); - let recorder = new SequenceRecorder(this.controller); + let playbackEngine = new InputSequenceSimulator(controller); + let recorder = new SequenceRecorder(controller); let layout = new FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); - this.controller.layoutConfiguration = layout; + controller.layoutConfiguration = layout; let fireEvent = () => { playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: -5, targetY: 15}, @@ -74,7 +71,7 @@ describe("Layer one - DOM -> InputSequence", function() { // This test is invalidated if the handler itself isn't called. So... let's verify that! // Not quite covered by the canary cases b/c of the distinct targetElement. - let mouseEngine = this.controller.recognizer.mouseEngine; + let mouseEngine = controller.recognizer.mouseEngine; let trueHandler = mouseEngine.onMouseStart; let fakeHandler = mouseEngine.onMouseStart = sinon.fake(); fireEvent(); diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.js b/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.def.ts similarity index 81% rename from common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.js rename to common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.def.ts index 55105ead2a6..95af23b61da 100644 --- a/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.js +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.def.ts @@ -1,32 +1,40 @@ -import { assert, expect } from '../../../../../../../../node_modules/chai/chai.js'; +import { assert, expect } from 'chai'; +import sinon from 'sinon'; + +import type { GestureDebugSource, InputSample, SerializedGestureSource } from '@keymanapp/gesture-recognizer'; import { HostFixtureLayoutController, InputSequenceSimulator -} from '../../../../../build/tools/lib/index.mjs'; +} from '#tools'; function isOnAndroid() { const agent=navigator.userAgent; return agent.indexOf('Android' >= 0); } +const loc = document.location; +// config.testFile generally starts with a '/', with the path resembling the actual full local +// filesystem for the drive. +const domain = `${loc.protocol}/${loc.host}` + +async function fetchRecording(jsonFilename) { + const jsonResponse = await fetch(new URL(`${domain}/resources/json/${jsonFilename}.json`)); + return await jsonResponse.json(); +} + describe("Layer one - DOM -> InputSequence", function() { - this.timeout(testconfig.timeouts.standard); + this.timeout(20000); - before(function() { - fixture.setBase(''); - }); + let controller: HostFixtureLayoutController; beforeEach(function(done) { - fixture.load('host-fixture.html'); - this.controller = new HostFixtureLayoutController(); - this.controller.connect().then(() => done()); + controller = new HostFixtureLayoutController(); + controller.connect().then(() => done()); }); afterEach(function() { - this.controller.destroy(); - fixture.cleanup(); - fixture.cleanup(); + controller.destroy(); }); describe('recorded input sequences', function() { @@ -40,9 +48,7 @@ describe("Layer one - DOM -> InputSequence", function() { // We rely on this function to have the same context as `it` - the test-definition function. let replayAndCompare = function(testObj) { - let resultPromise; - - let playbackEngine = new InputSequenceSimulator(this.controller); + let playbackEngine = new InputSequenceSimulator(controller); // ********************************** // Android-Chrome sequence simulation does not allow fractional values in MouseEvent clientX/clientY... @@ -58,7 +64,7 @@ describe("Layer one - DOM -> InputSequence", function() { } } - resultPromise = playbackEngine.replayAsync(testObj); + let resultPromise = playbackEngine.replayAsync(testObj); // replayAsync sets up timeouts against the `clock` object. // This will run through the simulated timeout queue asynchronously. @@ -102,8 +108,8 @@ describe("Layer one - DOM -> InputSequence", function() { // Now to compare just the timestamp elements. We'll tolerate a difference of up to 1. // Note: if using the `replaySync` function instead, disable this section! // (Through to the nested for-loop `assert.closeTo`) - let sampleTimeExtractor = (sample) => sample.t; - let inputTimeExtractor = (input) => { + let sampleTimeExtractor = (sample: InputSample) => sample.t; + let inputTimeExtractor = (input: GestureDebugSource | SerializedGestureSource) => { return input.path.coords.map(sampleTimeExtractor); } @@ -140,9 +146,8 @@ describe("Layer one - DOM -> InputSequence", function() { ]; for(let recordingID of testRecordings) { - it(`${recordingID}.json`, function() { - this.timeout(2 * testconfig.timeouts.standard); - let testObj = __json__['receiver/' + recordingID]; + it(`${recordingID}.json`, async function() { + let testObj = await fetchRecording('receiver/' + recordingID); // 'describe' has a notably different `this` reference than `it`, `before`, etc, // hence the `.call` construction. diff --git a/common/web/gesture-recognizer/src/test/auto/browser/manual.conf.cjs b/common/web/gesture-recognizer/src/test/auto/browser/manual.conf.cjs deleted file mode 100644 index 2c9e939f08f..00000000000 --- a/common/web/gesture-recognizer/src/test/auto/browser/manual.conf.cjs +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = function(config) { - var base = require("./base.conf.cjs"); - - var specifics = { - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['mocha'], - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Firefox', 'Chrome'], // Can be specified at run-time instead! - // Future note for us: https://www.npmjs.com/package/karma-browserstack-launcher - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. - logLevel: config.LOG_INFO - }; - - config.set(Object.assign(specifics, base)); -} diff --git a/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs b/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs new file mode 100644 index 00000000000..d18a8a9cb09 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.CI.config.mjs @@ -0,0 +1,13 @@ +// @ts-check +import BASE_CONFIG from './web-test-runner.config.mjs'; +import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; +import { sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; + +/** @type {import('@web/test-runner').TestRunnerConfig} */ +export default { + ...BASE_CONFIG, + reporters: [ + teamcityReporter(), /* custom-written, for CI-friendly reports */ + sessionStabilityReporter({ciMode: true}) + ] +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs b/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs new file mode 100644 index 00000000000..0d5bac85080 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/web-test-runner.config.mjs @@ -0,0 +1,69 @@ +// @ts-check +import { devices, playwrightLauncher } from '@web/test-runner-playwright'; +import { defaultReporter, summaryReporter } from '@web/test-runner'; +import { LauncherWrapper, sessionStabilityReporter } from '@keymanapp/common-test-resources/test-runner-stability-reporter.mjs'; +import named from '@keymanapp/common-test-resources/test-runner-rename-browser.mjs' +import { esbuildPlugin } from '@web/dev-server-esbuild'; +import { importMapsPlugin } from '@web/dev-server-import-maps'; +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; + +const dir = dirname(fileURLToPath(import.meta.url)); +const KEYMAN_ROOT = resolve(dir, '../../../../../../../'); + +/** @type {import('@web/test-runner').TestRunnerConfig} */ +export default { + // debug: true, + browsers: [ + new LauncherWrapper(playwrightLauncher({ product: 'chromium' })), + new LauncherWrapper(playwrightLauncher({ product: 'firefox' })), + playwrightLauncher({ product: 'webkit', concurrency: 1}), + named(new LauncherWrapper(playwrightLauncher({ product: 'webkit', concurrency: 1, createBrowserContext({browser}) { + return browser.newContext({...devices['iPhone X'] }); + }})), 'iOS Phone (emulated)'), + named(new LauncherWrapper(playwrightLauncher({ product: 'chromium' , createBrowserContext({browser}) { + return browser.newContext({...devices['Pixel 4'] }) + }})), 'Android Phone (emulated)'), + ], + concurrency: 10, + nodeResolve: true, + files: [ + '**/*.spec.html' + ], + middleware: [ + // Rewrites short-hand paths for test resources, making them fully relative to the repo root. + function rewriteResourcePath(context, next) { + if(context.url.startsWith('/resources/')) { + context.url = '/common/web/gesture-recognizer/src/test' + context.url; + } + + return next(); + } + ], + plugins: [ + esbuildPlugin({ ts: true, target: 'auto'}), + importMapsPlugin({ + inject: { + importMap: { + // Redirects `eventemitter3` imports to the bundled ESM library. The standard import is an + // ESM wrapper around the CommonJS implementation, and WTR fails when it hits the CommonJS. + imports: { + 'eventemitter3': '/node_modules/eventemitter3/dist/eventemitter3.esm.js' + } + } + } + }) + ], + reporters: [ + summaryReporter({}), /* local-dev mocha-style */ + sessionStabilityReporter({}), + defaultReporter({}) + ], + /* + Un-comment the next two lines for easy interactive debugging; it'll launch the + test page in your preferred browser. + */ + // open: true, + // manual: true, + rootDir: KEYMAN_ROOT +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/tools/host-fixture/host-fixture.html b/common/web/gesture-recognizer/src/tools/host-fixture/host-fixture.html index e977e8377ac..2565ac5c4b0 100644 --- a/common/web/gesture-recognizer/src/tools/host-fixture/host-fixture.html +++ b/common/web/gesture-recognizer/src/tools/host-fixture/host-fixture.html @@ -11,6 +11,8 @@ Gesture Recognizer - Fixture Host Page + + -
@@ -39,7 +40,6 @@

- diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts index d698ad6b5dd..880d93fddd3 100644 --- a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts +++ b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts @@ -96,13 +96,14 @@ export class InputSequenceSimulator { }; if(window['Touch'] !== undefined) { - touch = new Touch(touchDict); - } else { - // When not performing touch-emulation, some desktop browsers will leave `Touch` undefined. - touch = touchDict as any; + try { + touch = new Touch(touchDict); + return touch; + } catch {} } - return touch; + // When not performing touch-emulation, some desktop browsers will leave `Touch` undefined. + return touchDict as any; }); // Now that we've removed the entries that match any changed touchpoints, filter out any null entries. @@ -119,11 +120,13 @@ export class InputSequenceSimulator { let buildEvent = (type: string, dict: TouchEventInit) => { if(window['TouchEvent'] !== undefined) { // Nothing beats the real thing. - return new TouchEvent(type, dict); - } else { - // Effectively, an internal polyfill. But, if at ALL possible, use a real version instead! - return this.buildSyntheticTouchEvent(type, dict); + try { + return new TouchEvent(type, dict); + } catch {}; } + + // Effectively, an internal polyfill. But, if at ALL possible, use a real version instead! + return this.buildSyntheticTouchEvent(type, dict); } switch(state) { diff --git a/common/web/gesture-recognizer/test.sh b/common/web/gesture-recognizer/test.sh index 4dc6251e452..dca49780a15 100755 --- a/common/web/gesture-recognizer/test.sh +++ b/common/web/gesture-recognizer/test.sh @@ -49,17 +49,15 @@ test-headless ( ) { } test-browser ( ) { - KARMA_FLAGS= + local WTR_DEBUG= + local WTR_CONFIG= if [[ $# -eq 1 && $1 == "debug" ]]; then - KARMA_CONFIG="manual.conf.cjs" - KARMA_FLAGS="--no-single-run" - elif [ $REPORT_STYLE == "local" ]; then - KARMA_CONFIG="manual.conf.cjs" - else - KARMA_CONFIG="CI.conf.cjs" + WTR_DEBUG=" --manual" + elif [ $REPORT_STYLE != "local" ]; then + WTR_CONFIG=.CI fi - karma start src/test/auto/browser/$KARMA_CONFIG "$KARMA_FLAGS" + web-test-runner --config src/test/auto/browser/web-test-runner${WTR_CONFIG}.config.mjs ${WTR_DEBUG} } if builder_start_action test:headless; then diff --git a/package-lock.json b/package-lock.json index 5b7a272c368..a5565385a1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -191,20 +191,6 @@ }, "devDependencies": { "@keymanapp/resources-gosh": "*", - "@types/sinon": "^10.0.13", - "karma": "^6.4.1", - "karma-browserstack-launcher": "^1.6.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-firefox-launcher": "^1.1.0", - "karma-fixture": "^0.2.6", - "karma-html2js-preprocessor": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-fixtures-preprocessor": "0.0.6", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-teamcity-reporter": "^1.1.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "promise-status-async": "^1.2.10",