diff --git a/package-lock.json b/package-lock.json index bd0d3903d1..68a5a5cd99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2404,6 +2404,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz", + "integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.46.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@puppeteer/browsers": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", @@ -14161,6 +14177,7 @@ }, "devDependencies": { "@keymanapp/resources-gosh": "*", + "@playwright/test": "^1.46.1", "@sentry/cli": "^2.31.0", "@zip.js/zip.js": "^2.7.32", "c8": "^7.12.0", diff --git a/web/.gitignore b/web/.gitignore index d7d1e04036..7e33d0cd87 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -11,4 +11,8 @@ node_modules/ source/environment.inc.ts **/.idea/**/*.xml **/*.iml -**/*.kpj.user \ No newline at end of file +**/*.kpj.user + +# Playwright +playwright-report/ +test-results/ diff --git a/web/build.sh b/web/build.sh index 532c367895..d047b7dece 100755 --- a/web/build.sh +++ b/web/build.sh @@ -40,6 +40,7 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ \ "--test-dom-only For test, run only DOM-oriented unit tests (reduced footprint, nothing browser-specific)" \ "--test-integrated-only For test, run only KMW's integration test suite" \ + "--test-e2e-only For test, run only KMW's end-to-end test suite" \ "--test-inspect For test, run browser-based unit tests in an inspectable mode" builder_parse "$@" @@ -168,6 +169,24 @@ builder_run_action build:_all build_tests_action # Run tests builder_run_child_actions test +function is_test_included() { + local test_opt=$1 + local run_dom_tests=true run_integrated_tests=true run_e2e_tests=true + local var_name="run_${test_opt}_tests" + + if builder_has_option --test-integrated-only || builder_has_option --test-e2e-only; then + run_dom_tests=false + fi + if builder_has_option --test-dom-only || builder_has_option --test-e2e-only; then + run_integrated_tests=false + fi + if builder_has_option --test-dom-only || builder_has_option --test-integrated-only; then + run_e2e_tests=false + fi + + ${!var_name} +} + function do_browser_tests() { # Browser-based tests: common configs & kill-switches @@ -175,6 +194,7 @@ function do_browser_tests() { local WTR_CONFIG= if builder_is_ci_build; then WTR_CONFIG=.CI + export KEYMAN_IS_CI_BUILD=1 fi # Prepare the flags for the karma command. @@ -184,12 +204,15 @@ function do_browser_tests() { fi pushd "${KEYMAN_ROOT}" - if builder_has_option --test-dom-only || ! builder_has_option --test-integrated-only; then + if is_test_included dom; then web-test-runner --config "web/src/test/auto/dom/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_INSPECT} fi - if builder_has_option --test-integrated-only || ! builder_has_option --test-dom-only; then + if is_test_included integrated; then web-test-runner --config "web/src/test/auto/integrated/web-test-runner${WTR_CONFIG}.config.mjs" ${WTR_INSPECT} fi + if is_test_included e2e; then + npx playwright test --config "web/src/test/auto/e2e/playwright.config.ts" + fi popd } diff --git a/web/package.json b/web/package.json index 5567e44f48..219b2ddac9 100644 --- a/web/package.json +++ b/web/package.json @@ -104,6 +104,7 @@ "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { "@keymanapp/resources-gosh": "*", + "@playwright/test": "^1.46.1", "@sentry/cli": "^2.31.0", "@zip.js/zip.js": "^2.7.32", "c8": "^7.12.0", @@ -116,9 +117,9 @@ "test": "gosh ./test.sh" }, "dependencies": { + "@keymanapp/common-types": "*", "@keymanapp/keyman-version": "*", "@keymanapp/lexical-model-layer": "*", - "@keymanapp/common-types": "*", "@keymanapp/recorder-core": "*", "core-js": "^3.34.0", "eventemitter3": "^5.0.0", diff --git a/web/src/test/auto/e2e/README.md b/web/src/test/auto/e2e/README.md new file mode 100644 index 0000000000..39880c6dec --- /dev/null +++ b/web/src/test/auto/e2e/README.md @@ -0,0 +1,18 @@ +# Keyman Engine for Web e2e tests + +These tests are run as part of the regular build, but can be run separately +with: + +```bash +web/test.sh test:e2e +``` + +The Playwright Test for VSCode extension (ms-playwright.playwright) allows to run +the tests in VSCode. + +It's also possible to run them with a UI that shows screenshots +captured while running the tests: + +```bash +npx playwright test --config "web/src/test/auto/e2e/playwright.config.ts" --ui +``` diff --git a/web/src/test/auto/e2e/keyboard.tests.ts b/web/src/test/auto/e2e/keyboard.tests.ts new file mode 100644 index 0000000000..b51dc3cbcb --- /dev/null +++ b/web/src/test/auto/e2e/keyboard.tests.ts @@ -0,0 +1,66 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + */ +import { test, expect, Page, Locator } from '@playwright/test'; + +test.describe('KMX keyboards', function () { + let textarea: Locator; + const beforeEach = async (page: Page) => { + await page.goto('http://localhost:3000/src/test/manual/web/kmxkeyboard.html?keyboard=kmx'); + await page.evaluate(async () => { await window.KmwLoaded; }); + textarea = page.locator('#inputarea'); + await textarea.click(); + // we don't have a OSK yet, so don't wait for it to appear + } + + test('can type on a (simulated) hardware keyboard', async ({ page }) => { + await beforeEach(page); + await page.keyboard.press('x'); + await page.keyboard.press('j'); + await page.keyboard.press('m'); + await page.keyboard.press('Shift+e'); + await page.keyboard.press('r'); + await expect(textarea).toHaveValue('ខ្មែរ', { timeout: 500 }); + }); + + test('can type on a (simulated) hardware keyboard with reordering', async ({ page }) => { + await beforeEach(page); + await page.keyboard.press('x'); + await page.keyboard.press('Shift+e'); + await page.keyboard.press('j'); + await page.keyboard.press('m'); + await page.keyboard.press('r'); + await expect(textarea).toHaveValue('ខ្មែរ', { timeout: 500 }); + }); +}); + +test.describe('JS keyboards', function () { + let textarea: Locator; + const beforeEach = async (page: Page) => { + await page.goto('http://localhost:3000/src/test/manual/web/kmxkeyboard.html?keyboard=js'); + await page.evaluate(async () => { await window.KmwLoaded; }); + textarea = page.locator('#inputarea'); + await textarea.click(); + await page.evaluate(async () => { await window.OskShown; }); + }; + + test('can type on a (simulated) hardware keyboard', async ({ page }) => { + await beforeEach(page); + await page.keyboard.press('x'); + await page.keyboard.press('j'); + await page.keyboard.press('m'); + await page.keyboard.press('Shift+e'); + await page.keyboard.press('r'); + await expect(textarea).toHaveValue('ខ្មែរ', { timeout: 500 }); + }); + + test('can type on a (simulated) hardware keyboard with reordering', async ({ page }) => { + await beforeEach(page); + await page.keyboard.press('x'); + await page.keyboard.press('Shift+e'); + await page.keyboard.press('j'); + await page.keyboard.press('m'); + await page.keyboard.press('r'); + await expect(textarea).toHaveValue('ខ្មែរ', { timeout: 500 }); + }); +}); \ No newline at end of file diff --git a/web/src/test/auto/e2e/playwright.config.ts b/web/src/test/auto/e2e/playwright.config.ts new file mode 100644 index 0000000000..47b6ceac4d --- /dev/null +++ b/web/src/test/auto/e2e/playwright.config.ts @@ -0,0 +1,84 @@ +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig, devices } from '@playwright/test'; + +const dir = dirname(fileURLToPath(import.meta.url)); +const KEYMAN_ROOT = resolve(dir, '../../../../../'); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + rootDir: KEYMAN_ROOT, + testDir: '.', + testMatch: '*.tests.ts', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.KEYMAN_IS_CI_BUILD, + /* Retry on CI only */ + retries: process.env.KEYMAN_IS_CI_BUILD ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.KEYMAN_IS_CI_BUILD ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + // reporter: process.env.KEYMAN_IS_CI_BUILD ? [ + // [KEYMAN_ROOT + '/common/test/resources/playwright-TC-reporter.ts', { parentFlow: 'web_tests' }] + // ] : [ + reporter: [ + ['list'], + ['html', { open: 'never' }], + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('')`. */ + // baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: `${KEYMAN_ROOT}/web/build.sh start`, + url: 'http://localhost:3000', + reuseExistingServer: !process.env.KEYMAN_IS_CI_BUILD, + }, +}); diff --git a/web/src/test/manual/web/kmxkeyboard.html b/web/src/test/manual/web/kmxkeyboard.html index 8510324f9f..c5a083b990 100644 --- a/web/src/test/manual/web/kmxkeyboard.html +++ b/web/src/test/manual/web/kmxkeyboard.html @@ -12,7 +12,7 @@ - KeymanWeb Sample Page - Unminified Source + KeymanWeb Khmer KMX/JS test page