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
+
+
-
-
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",