test(web): add e2e tests for examples from guide

Build-bot: skip build:web
Test-bot: skip
This commit is contained in:
Eberhard Beilharz 2026-05-29 19:31:00 +02:00
parent 58a565be21
commit bda4b50e2a
No known key found for this signature in database
GPG key ID: E9140597606020D3
8 changed files with 353 additions and 15 deletions

View file

@ -114,6 +114,21 @@ build_tests_action() {
cp "${KEYMAN_ROOT}/web/src/test/auto/dom/cases/attachment/textStoreForElement.tests.html" \
"${KEYMAN_ROOT}/web/build/test/dom/cases/attachment/"
# Copy and update guide examples - for local, PR, and test builds we
# replace the CDN URL with the local build path, so that we can test
# against the current build
mkdir -p "${KEYMAN_ROOT}/web/build/docs/engine/guide"
cp -r "${KEYMAN_ROOT}/web/docs/engine/guide/examples" \
"${KEYMAN_ROOT}/web/build/docs/engine/guide/"
# shellcheck disable=SC2310
if ! builder_is_ci_release_build; then
for f in "${KEYMAN_ROOT}/web/build/docs/engine/guide/examples"/*.html; do
sed "s|https://s\.keyman\.com/kmw/engine/[0-9]*\.[0-9]*\.[0-9]*/|/build/publish/${config}/|g" \
"${f}" > "${f}.tmp" && mv "${f}.tmp" "${f}"
done
fi
}
coverage_action() {

View file

@ -19,8 +19,8 @@
<body>
<h1>Automatic Mode Example</h1>
<form action='.' method='post'>
<p><input type='text' id='multilingual' name='multilingual' size="40" /></p>
<p><textarea cols='40' rows='5'></textarea></p>
<p><input type='text' id='multilingual' name='multilingual' size="40" data-testid='multilingual'/></p>
<p><textarea cols='40' rows='5' data-testid='textarea'></textarea></p>
</form>
<a href="automatic-control">Back to Document</a>

View file

@ -24,13 +24,13 @@ keyman.init().then(async function() {
}
document.f.multilingual.focus();
keyman.setActiveKeyboard('', '');
await keyman.setActiveKeyboard('', '');
});
function KWControlChange() {
async function KWControlChange() {
var name = KWControl.value.substr(0, KWControl.value.indexOf("$$"));
var languageCode = KWControl.value.substr(KWControl.value.indexOf("$$") + 2);
keyman.setActiveKeyboard(name, languageCode);
await keyman.setActiveKeyboard(name, languageCode);
document.f.multilingual.focus();
}
@ -40,7 +40,7 @@ function KWControlChange() {
<h1>Manual Control - Custom Interface</h1>
<form name='f' action='.' method='post'>
<p>Keyboard: <select id='KWControl' onchange='KWControlChange()'><option value=''>English</option></select></p>
<p><label for='KWControl'>Keyboard:</label> <select id='KWControl' onchange='KWControlChange()'><option value=''>English</option></select></p>
<p><input type='text' id='multilingual' name='multilingual' size="40" placeholder="id='multilingual'"/></p>
</form>

View file

@ -11,7 +11,7 @@
languages: { id: 'lo', name: 'Lao' },
filename: "./js/laokeys.js"
});
keyman.setActiveKeyboard('laokeys');
await keyman.setActiveKeyboard('laokeys');
keyman.osk.hide();
});
@ -30,8 +30,8 @@
<p><img style="border: solid 1px black; padding: 2px 2px 3px 2px" src='js/kmicon.png' alt='KeymanWeb' onclick='KWControlClick()' id='KWControl' /></p>
<p><input type='text' id='multilingual' name='multilingual' size="40" /></p>
<p><textarea cols='40' rows='5'></textarea></p>
<p><input type='text' id='multilingual' name='multilingual' size="40" data-testid='multilingual'/></p>
<p><textarea cols='40' rows='5' data-testid='textarea'></textarea></p>
<a href="manual-control">Back to Document</a>
</form>

View file

@ -28,15 +28,15 @@ Include the following script in the HEAD of your page:
}
document.f.multilingual.focus();
keyman.setActiveKeyboard('', '');
await keyman.setActiveKeyboard('', '');
});
/* KWControlChange: Called when user selects an item in the KWControl SELECT */
function KWControlChange() {
async function KWControlChange() {
/* Select the keyboard in KeymanWeb */
var name = KWControl.value.substr(0, KWControl.value.indexOf("$$"));
var languageCode = KWControl.value.substr(KWControl.value.indexOf("$$"+2));
keyman.setActiveKeyboard(name, languageCode);
var languageCode = KWControl.value.substr(KWControl.value.indexOf("$$") + 2);
await keyman.setActiveKeyboard(name, languageCode);
/* Focus onto the multilingual field in the form */
document.f.multilingual.focus();
}

View file

@ -2,7 +2,10 @@
title: Manual Mode Example
---
In this example, the web page designer specifies when KeymanWeb's on-screen keyboard may be displayed on non-mobile devices. They have also specified that the LaoKeys keyboard should be activated by default. This example continues to use the KeymanWeb default interface. Please click [this link](__manual-control.html) to open the test page.
In this example, the web page designer specifies when KeymanWeb's on-screen keyboard may be
displayed on non-mobile devices. They have also specified that the LaoKeys keyboard should be
activated by default. This example continues to use the KeymanWeb default interface. Please click
[this link](__manual-control.html) to open the test page.
## Code Walkthrough
@ -17,7 +20,7 @@ Include the following script in the HEAD of your page:
languages:{id:'lo',name:'Lao'},
filename: "./js/laokeys.js"
});
keyman.setActiveKeyboard('laokeys');
await keyman.setActiveKeyboard('laokeys');
keyman.osk.hide();
});

View file

@ -0,0 +1,64 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/
import { type Locator, type Page } from "@playwright/test";
/**
* Expands the keyboard selection menu and returns the text content of the
* currently selected keyboard.
*/
export async function getSelectedKeyboardMenuText(page: Page): Promise<string | undefined> {
const watchDog = page.waitForFunction(() => !!document.getElementById('KeymanWeb_KbdList'));
await page.getByRole('img', { name: 'Use Web Keyboard' }).click();
await watchDog;
return page.evaluate(() => {
const selectedKbd = document.querySelector('#kmwico .selected');
return selectedKbd?.textContent;
});
};
/**
* Expands the keyboard selection menu and returns the menu items as an array
*/
export async function getAllKeyboardMenuText(page: Page): Promise<(string|undefined)[]> {
const watchDog = page.waitForFunction(() => !!document.getElementById('KeymanWeb_KbdList'));
await page.getByRole('img', { name: 'Use Web Keyboard' }).hover();
await watchDog;
return page.evaluate(() => {
const menuItems = [];
const menuDiv = document.querySelector('#kmwico');
const kbdList = menuDiv?.lastElementChild;
for (let i = 0; i < (kbdList ? kbdList.children.length : 0); i++) {
const item = kbdList?.children[i];
menuItems.push(item?.textContent);
}
return menuItems;
});
}
/**
* Loads the specified URL and waits for the page load event.
*/
export async function loadPage(page: Page, url: string): Promise<Page> {
const loadPromise = page.waitForEvent('load');
await page.goto(url);
return loadPromise;
}
/**
* Clicks the specified field and waits for the OSK to be shown, returning a
* locator for the OSK title bar.
*/
export async function clickFieldAndWaitForOSK(page: Page, fieldLocator: Locator): Promise<Locator> {
const keyboardchangePromise = page.evaluate(async () => {
return new Promise((resolve) => {
keyman.addEventListener('keyboardchange', function (kbd) {
resolve(kbd);
});
});
});
await fieldLocator.click();
await keyboardchangePromise;
return page.locator('#keymanweb_title_bar');
}

View file

@ -0,0 +1,256 @@
/*
* Keyman is copyright (C) SIL Global. MIT License.
*/
import { test, expect, type Page } from '@playwright/test';
import { clickFieldAndWaitForOSK, getAllKeyboardMenuText, getSelectedKeyboardMenuText, loadPage } from './e2eUtils';
async function setTimeoutAndLoadPage(page: Page, url: string): Promise<void> {
test.setTimeout(5000);
await loadPage(page, url);
}
test.describe('First example from the guide', function () {
const beforeEach = async (page: Page) => {
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__first-example.html');
}
test('Input field shows US keyboard', async ({ page }) => {
// Setup
await beforeEach(page);
const oskTitleBar = await clickFieldAndWaitForOSK(page, page.getByPlaceholder('Hello World'));
// Verify OSK shows US keyboard
await expect(page.getByRole('img', { name: 'Use Web Keyboard' })).toBeVisible();
await expect(page.getByRole('img', { name: 'Show On Screen Keyboard' })).toBeVisible();
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(oskTitleBar).toContainText('US');
await expect(await getSelectedKeyboardMenuText(page)).toBe('English - US');
});
test('Keyman menu has expected keyboards', async ({ page }) => {
// Setup
await beforeEach(page);
await clickFieldAndWaitForOSK(page, page.getByPlaceholder('Hello World'));
// Verify OSK menu has expected entries
await expect(page.getByRole('img', { name: 'Use Web Keyboard' })).toBeVisible();
await expect(page.getByRole('img', { name: 'Show On Screen Keyboard' })).toBeVisible();
await expect(await getAllKeyboardMenuText(page)).toEqual(['(System keyboard)', 'English - US', 'Thai - Thai Kedmanee Basic'])
});
});
test.describe('Auto-control example from the guide', function () {
const beforeEach = async (page: Page) => {
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__auto-control.html');
}
test('Input field shows Lao keyboard', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByTestId('multilingual' ).click();
// Verify OSK is shown
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.locator('#keymanweb_title_bar')).toContainText('Lao (Phonetic)');
});
test('Textarea shows Lao keyboard', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByTestId('textarea').click();
// Verify OSK is shown
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.locator('#keymanweb_title_bar')).toContainText('Lao (Phonetic)');
});
});
test.describe('Control-by-control example from the guide', function () {
const beforeEach = async (page: Page) => {
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__control-by-control.html');
}
test('address field does not have KeymanWeb enabled', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByPlaceholder('id = address').click();
// Verify OSK is not shown
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeFalsy();
await expect(page.getByRole('img', { name: 'Use Web Keyboard' })).not.toBeVisible();
await expect(page.getByRole('img', { name: 'Show On Screen Keyboard' })).not.toBeVisible();
});
// TODO: #16080
test.skip('subject field does not show keyboard and defaults to system keyboard', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByPlaceholder('id = subject').click();
// Verify OSK is shown
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.getByRole('img', { name: 'Use Web Keyboard' })).toBeVisible();
await expect(page.getByRole('img', { name: 'Show On Screen Keyboard' })).not.toBeVisible();
await expect(await getSelectedKeyboardMenuText(page)).toBe('(System keyboard)');
});
test('message body field shows Lao keyboard', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByPlaceholder('id = text').click();
// Verify OSK is shown
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.getByRole('img', { name: 'Use Web Keyboard' })).toBeVisible();
await expect(page.getByRole('img', { name: 'Show On Screen Keyboard' })).toBeVisible();
// Verify Lao (Phonetic) keyboard is active
await expect(page.locator('#keymanweb_title_bar')).toContainText('Lao (Phonetic)');
// Verify "Lao - Lao (Phonetic)" is selected (bold) in the menu
await expect(await getSelectedKeyboardMenuText(page)).toBe('Lao - Lao (Phonetic)');
});
});
test.describe('Full manual control example from the guide', function () {
const beforeEach = async (page: Page) => {
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__full-manual-control.html');
}
test('Shows English and no OSK after loading page', async ({ page }) => {
// Setup
await beforeEach(page);
// Verify 'English' selected (which has the value '') and no OSK showing
await expect(page.getByLabel('Keyboard')).toHaveValue('');
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).not.toBeTruthy();
});
test('Selecting English keyboard shows no OSK', async ({ page }) => {
// Setup
await beforeEach(page);
// first switch to Hebrew
let keyboardchangePromise = page.evaluate(async () => {
return new Promise((resolve) => {
keyman.addEventListener('keyboardchange', function (kbd) {
resolve(kbd);
});
});
});
await page.getByLabel('Keyboard').selectOption('Hebrew');
await keyboardchangePromise;
// then back to English
keyboardchangePromise = page.evaluate(async () => {
return new Promise((resolve) => {
keyman.addEventListener('keyboardchange', function (kbd) {
resolve(kbd);
});
});
});
await page.getByLabel('Keyboard').selectOption('English');
await keyboardchangePromise;
// Verify no OSK showing
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).not.toBeTruthy();
});
test('Selecting Devanagari keyboard shows Devanagari OSK', async ({ page }) => {
// Setup
await beforeEach(page);
const keyboardchangePromise = page.evaluate(async () => {
return new Promise((resolve) => {
keyman.addEventListener('keyboardchange', function (kbd) {
resolve(kbd);
});
});
});
await page.getByLabel('Keyboard').selectOption('Devanagari (INSCRIPT)');
await keyboardchangePromise;
// Verify Devanagari OSK showing
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.locator('#keymanweb_title_bar')).toContainText('Devanagari (INSCRIPT)');
});
test('Selecting Hebrew shows Hebrew OSK', async ({ page }) => {
// Setup
await beforeEach(page);
const keyboardchangePromise = page.evaluate(async () => {
return new Promise((resolve) => {
keyman.addEventListener('keyboardchange', function (kbd) {
resolve(kbd);
});
});
});
await page.getByLabel('Keyboard').selectOption('Hebrew');
await keyboardchangePromise;
// Verify Hebrew OSK showing
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.locator('#keymanweb_title_bar')).toContainText('Hebrew');
});
});
test.describe('Manual control example from the guide', function () {
const beforeEach = async (page: Page) => {
await setTimeoutAndLoadPage(page, 'http://localhost:3000/build/docs/engine/guide/examples/__manual-control.html');
}
test('Does not show OSK after loading', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByTestId('multilingual').click();
// Verify no OSK showing
await expect(await page.evaluate(() => keyman.osk.isEnabled())).not.toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).not.toBeTruthy();
});
test('Shows Lao OSK after clicking button', async ({ page }) => {
// Setup
await beforeEach(page);
await page.getByAltText('KeymanWeb').click();
await page.getByTestId('multilingual').click();
// Verify Lao OSK showing
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.locator('#keymanweb_title_bar')).toContainText('Lao');
});
test('Hides Lao OSK after clicking button', async ({ page }) => {
// Setup
await beforeEach(page);
// click button
await page.getByAltText('KeymanWeb').click();
// Verify Lao OSK showing
await page.getByTestId('multilingual').click();
await expect(await page.evaluate(() => keyman.osk.isEnabled())).toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).toBeTruthy();
await expect(page.locator('#keymanweb_title_bar')).toContainText('Lao');
// Click button again to hide OSK
await page.getByAltText('KeymanWeb').click();
// Verify Lao OSK not showing
await page.getByTestId('multilingual').click();
await expect(await page.evaluate(() => keyman.osk.isEnabled())).not.toBeTruthy();
await expect(await page.evaluate(() => keyman.osk.isVisible())).not.toBeTruthy();
});
});