mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
test: cover native Wayland Hangul in isolated CI (#19174)
* test: exercise native Wayland Hangul in isolated CI session * test: wait for nested compositor socket before selecting IBus * test: align Wayland IBus discovery with GNOME environment filtering * test: assert Wayland launch and register native Hangul evidence
This commit is contained in:
@@ -70,3 +70,40 @@ jobs:
|
||||
path: test-results/
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
linux-wayland:
|
||||
name: Linux Wayland Hangul terminating digit
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 25
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install native build, nested compositor and IME tools
|
||||
run: >-
|
||||
sudo apt-get update && sudo apt-get install -y
|
||||
build-essential python3 fonts-noto-cjk dbus-x11 dconf-gsettings-backend
|
||||
ibus ibus-hangul gnome-shell gnome-settings-daemon libglib2.0-bin
|
||||
xdotool xvfb x11-utils imagemagick
|
||||
- uses: ./.github/actions/install-node-dependencies
|
||||
with:
|
||||
native-runtime: electron
|
||||
- name: Build Electron app for E2E
|
||||
env:
|
||||
VITE_EXPOSE_STORE: 'true'
|
||||
run: |
|
||||
pnpm run build:relay
|
||||
pnpm exec electron-vite build --mode e2e
|
||||
pnpm run build:web-from-renderer
|
||||
- name: Run native Wayland Hangul terminating digit
|
||||
env:
|
||||
SKIP_BUILD: '1'
|
||||
run: node config/scripts/run-terminal-ibus-hangul-e2e.mjs --nested-wayland
|
||||
- name: Upload Wayland terminal IME evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: terminal-wayland-ime-evidence
|
||||
path: test-results/
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -18573,6 +18573,97 @@
|
||||
"Other released version pairs remain untested; not a required PR check."
|
||||
],
|
||||
"demotionRule": "Keep experimental if any direction skips or fails; do not extend timeouts or retry to green."
|
||||
},
|
||||
{
|
||||
"id": "terminal-input.native-wayland-hangul-digit",
|
||||
"title": "Native Wayland Hangul terminating digits reach the PTY exactly once",
|
||||
"maturity": "experimental",
|
||||
"protection": "partial",
|
||||
"owner": "terminal-input",
|
||||
"layer": "electron-native-ime-e2e",
|
||||
"surfaces": [
|
||||
"native Hangul composition",
|
||||
"Wayland terminal input"
|
||||
],
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"providers": [
|
||||
"local"
|
||||
],
|
||||
"coveredPlatforms": [
|
||||
"linux"
|
||||
],
|
||||
"coveredProviders": [
|
||||
"local"
|
||||
],
|
||||
"coverageNotes": "Ubuntu 22.04 nested GNOME and IBus Hangul drive three complete native executions in GitHub Actions. GNOME owns IBus; daemon and CLI share its default config discovery path.",
|
||||
"motivatingLinks": [
|
||||
"https://github.com/stablyai/orca/pull/19174"
|
||||
],
|
||||
"invariant": "Typing d k 1 Return through native IBus Hangul delivers exactly 아1 followed by newline without missing, duplicate, or reordered characters.",
|
||||
"oracle": "Three executions each assert three exact UTF-8 PTY lines. Verify the exact Playwright title, zero skips/retries, each individual native composition receipt, and the nested launch Wayland flag.",
|
||||
"commands": [
|
||||
"gh workflow run terminal-ime-e2e.yml",
|
||||
"gh run view 34074017928 --log",
|
||||
"pnpm exec playwright test --config tests/playwright.config.ts tests/e2e/terminal-hangul-terminating-digit-native.spec.ts --project=electron-headful --workers=1 --repeat-each=3 --retries=0 --reporter=list,json",
|
||||
"ORCA_BACKGROUND_LAUNCH=1 node_modules/.bin/vitest run --config config/vitest.config.ts config/scripts/terminal-ime-e2e-workflow.test.mjs"
|
||||
],
|
||||
"testFiles": [
|
||||
"tests/e2e/terminal-hangul-terminating-digit-native.spec.ts",
|
||||
"config/scripts/terminal-ime-e2e-workflow.test.mjs"
|
||||
],
|
||||
"assertionRefs": [
|
||||
{
|
||||
"file": "tests/e2e/terminal-hangul-terminating-digit-native.spec.ts",
|
||||
"assertions": [
|
||||
"a digit typed right after a Hangul syllable reaches the pty"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "config/scripts/terminal-ime-e2e-workflow.test.mjs",
|
||||
"assertions": [
|
||||
"runs native Wayland independently with CJK fonts and retained evidence"
|
||||
]
|
||||
}
|
||||
],
|
||||
"evidenceRuns": [
|
||||
{
|
||||
"date": "2026-09-07",
|
||||
"runner": "ci",
|
||||
"platform": "linux",
|
||||
"command": "gh run view 34074017928 --log",
|
||||
"result": "passed",
|
||||
"summary": "Permanent runner passed three native executions, nine exact lines and zero skips/retries. Downloaded participation report and all three engagement receipts verified; compositor cleanup reported no remaining group members. Independent X11 job passed.",
|
||||
"durationSeconds": 76.61
|
||||
}
|
||||
],
|
||||
"runtimeBudget": {
|
||||
"p95Seconds": 1500,
|
||||
"scope": "CI job timeout including installation/build; measured p95 not established"
|
||||
},
|
||||
"flakeHistory": {
|
||||
"status": "soaking",
|
||||
"evidence": "Earlier diagnostic repetition had one unexplained missing Hangul commit. GNOME-owned diagnostic and corrected permanent runner each passed 3/3. Long-term soak is missing."
|
||||
},
|
||||
"redGreenEvidence": {
|
||||
"status": "partial",
|
||||
"evidence": "Original exact-byte assertions retained. Permanent startup failed until the GNOME config-discovery mismatch was corrected. No intentional production regression was introduced."
|
||||
},
|
||||
"performanceBudget": {
|
||||
"required": false,
|
||||
"evidence": "CI-only harness; no application runtime changes."
|
||||
},
|
||||
"promotionCriteria": [
|
||||
"Collect 100 soak runs across 14 days with no unexplained flakes.",
|
||||
"Exercise native Wayland desktops beyond nested GNOME before broadening the claim."
|
||||
],
|
||||
"knownGaps": [
|
||||
"Only Hangul terminating digits; no native candidate-selection or other input-method coverage claim.",
|
||||
"No macOS, Windows, SSH terminal, packaged build, or mixed-version claim.",
|
||||
"Default config paths are shared with GNOME on a disposable hosted CI runner; nested mode refuses non-GitHub-Actions execution."
|
||||
],
|
||||
"demotionRule": "Keep experimental on unexplained failures; retain exact bytes and participation checks without retries, skips, or longer deadlines."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
[[ "${GITHUB_ACTIONS:-}" == true ]]
|
||||
# The isolated X server owns exactly one nested compositor window.
|
||||
mapfile -t windows < <(xwininfo -root -tree | awk '$2 == "\"gnome-shell\":" {print $1}')
|
||||
[[ ${#windows[@]} -eq 1 ]]
|
||||
xdotool windowmap --sync "${windows[0]}"
|
||||
xdotool windowfocus --sync "${windows[0]}"
|
||||
read -r width height < <(xwininfo -id "${windows[0]}" | awk '$1 == "Width:" {w=$2} $1 == "Height:" {print w,$2}')
|
||||
# The native spec opens a single terminal; a seat click activates its Wayland client.
|
||||
xdotool mousemove --window "${windows[0]}" "$((width / 2))" "$((height / 2))" click 1
|
||||
@@ -10,7 +10,7 @@ const NATIVE_IME_PRODUCT_SOURCE =
|
||||
|
||||
/** The harness itself: the session runner, the boundary probes, and the native specs. */
|
||||
const NATIVE_IME_HARNESS =
|
||||
/^(?:config\/scripts\/(?:run-terminal-ibus-hangul-e2e|terminal-ime-engagement-receipt)\.mjs$|tests\/e2e\/terminal-ime-(?:boundary-probe|byte-reader|engagement-receipt)\.ts$|tests\/e2e\/terminal-(?:ibus-hangul|hangul-terminating-digit|macos-2set-korean)-native\.spec\.ts$)/
|
||||
/^(?:config\/scripts\/focus-nested-wayland-terminal\.sh$|config\/scripts\/(?:run-terminal-ibus-hangul-e2e|terminal-ime-engagement-receipt)\.mjs$|tests\/e2e\/terminal-ime-(?:boundary-probe|byte-reader|engagement-receipt)\.ts$|tests\/e2e\/terminal-(?:ibus-hangul|hangul-terminating-digit|macos-2set-korean)-native\.spec\.ts$)/
|
||||
|
||||
export const PR_E2E_SOURCE_ROUTES = [
|
||||
{
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
readFileSync,
|
||||
writeFileSync
|
||||
} from 'node:fs'
|
||||
import { verifyPlaywrightParticipation } from './verify-playwright-participation.mjs'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import {
|
||||
@@ -20,6 +21,9 @@ import {
|
||||
const projectDir = path.resolve(import.meta.dirname, '../..')
|
||||
const scriptPath = import.meta.filename
|
||||
const insideSessionFlag = '--inside-session'
|
||||
const nestedWaylandFlag = '--nested-wayland'
|
||||
const nestedWayland = process.argv.includes(nestedWaylandFlag)
|
||||
const waylandTitle = 'a digit typed right after a Hangul syllable reaches the pty'
|
||||
const processStopTimeoutMs = 5_000
|
||||
const processKillTimeoutMs = 1_000
|
||||
|
||||
@@ -111,26 +115,38 @@ function configureHangulEngine() {
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForHangulEngine(ibusProcess) {
|
||||
async function waitForHangulEngine(sessionProcess) {
|
||||
let lastError = ''
|
||||
const deadline = Date.now() + 15_000
|
||||
while (Date.now() < deadline) {
|
||||
if (ibusProcess.exitCode !== null) {
|
||||
throw new Error(`ibus-daemon exited early with code ${ibusProcess.exitCode}`)
|
||||
if (sessionProcess.exitCode !== null) {
|
||||
throw new Error(`IME session process exited early with code ${sessionProcess.exitCode}`)
|
||||
}
|
||||
const result = spawnSync('ibus', ['engine', 'hangul'], { stdio: 'pipe' })
|
||||
if (
|
||||
nestedWayland &&
|
||||
!existsSync(path.join(process.env.XDG_RUNTIME_DIR, process.env.WAYLAND_DISPLAY))
|
||||
) {
|
||||
await delay(100)
|
||||
continue
|
||||
}
|
||||
const result = spawnSync('ibus', ['engine', 'hangul'], { encoding: 'utf8' })
|
||||
lastError = result.stderr?.trim() || String(result.error ?? result.status)
|
||||
if (result.status === 0) {
|
||||
return
|
||||
}
|
||||
await delay(100)
|
||||
}
|
||||
throw new Error('Timed out while selecting the IBus Hangul engine')
|
||||
throw new Error(`Timed out while selecting the IBus Hangul engine: ${lastError}`)
|
||||
}
|
||||
|
||||
async function runInsideSession(evidenceDir) {
|
||||
const receiptPath = path.join(evidenceDir, 'ime-engagement-receipt.jsonl')
|
||||
const ibusLogPath = path.join(evidenceDir, 'ibus-daemon.log')
|
||||
const ibusLogFd = openSync(ibusLogPath, 'w')
|
||||
const windowManagerLogPath = path.join(evidenceDir, 'xfwm4.log')
|
||||
const windowManagerLogPath = path.join(
|
||||
evidenceDir,
|
||||
nestedWayland ? 'gnome-shell.log' : 'xfwm4.log'
|
||||
)
|
||||
const windowManagerLogFd = openSync(windowManagerLogPath, 'w')
|
||||
const evidence = {
|
||||
display: process.env.DISPLAY ?? null,
|
||||
@@ -147,32 +163,58 @@ async function runInsideSession(evidenceDir) {
|
||||
|
||||
try {
|
||||
configureHangulEngine()
|
||||
windowManagerProcess = spawn('xfwm4', ['--compositor=off'], {
|
||||
detached: true,
|
||||
env: process.env,
|
||||
stdio: ['ignore', windowManagerLogFd, windowManagerLogFd]
|
||||
})
|
||||
if (!windowManagerProcess.pid) {
|
||||
throw new Error('xfwm4 did not return a PID')
|
||||
}
|
||||
evidence.windowManagerPid = windowManagerProcess.pid
|
||||
console.error(`[terminal-ime] started xfwm4 PID ${windowManagerProcess.pid}`)
|
||||
|
||||
ibusProcess = spawn(
|
||||
'ibus-daemon',
|
||||
['--xim', '--verbose', '--panel=disable', '--emoji-extension=disable'],
|
||||
{
|
||||
if (nestedWayland) {
|
||||
for (const [schema, key, value] of [
|
||||
['org.gnome.desktop.interface', 'enable-animations', 'false'],
|
||||
['org.gnome.desktop.input-sources', 'sources', "[('ibus', 'hangul')]"]
|
||||
]) {
|
||||
const result = spawnSync('gsettings', ['set', schema, key, value], { encoding: 'utf8' })
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to configure GNOME: ${result.stderr}`)
|
||||
}
|
||||
}
|
||||
windowManagerProcess = spawn(
|
||||
'gnome-shell',
|
||||
['--nested', '--wayland', `--wayland-display=${process.env.WAYLAND_DISPLAY}`],
|
||||
{
|
||||
detached: true,
|
||||
env: process.env,
|
||||
stdio: ['ignore', windowManagerLogFd, windowManagerLogFd]
|
||||
}
|
||||
)
|
||||
} else {
|
||||
windowManagerProcess = spawn('xfwm4', ['--compositor=off'], {
|
||||
detached: true,
|
||||
env: process.env,
|
||||
stdio: ['ignore', ibusLogFd, ibusLogFd]
|
||||
stdio: ['ignore', windowManagerLogFd, windowManagerLogFd]
|
||||
})
|
||||
}
|
||||
if (!windowManagerProcess.pid) {
|
||||
throw new Error('Window manager did not return a PID')
|
||||
}
|
||||
evidence.windowManagerPid = windowManagerProcess.pid
|
||||
console.error(`[terminal-ime] started window manager PID ${windowManagerProcess.pid}`)
|
||||
|
||||
if (nestedWayland) {
|
||||
// GNOME starts IBus in the private session; a second daemon can compete for ownership.
|
||||
await waitForHangulEngine(windowManagerProcess)
|
||||
} else {
|
||||
ibusProcess = spawn(
|
||||
'ibus-daemon',
|
||||
['--xim', '--verbose', '--panel=disable', '--emoji-extension=disable'],
|
||||
{
|
||||
detached: true,
|
||||
env: process.env,
|
||||
stdio: ['ignore', ibusLogFd, ibusLogFd]
|
||||
}
|
||||
)
|
||||
if (!ibusProcess.pid) {
|
||||
throw new Error('ibus-daemon did not return a PID')
|
||||
}
|
||||
)
|
||||
if (!ibusProcess.pid) {
|
||||
throw new Error('ibus-daemon did not return a PID')
|
||||
evidence.ibusDaemonPid = ibusProcess.pid
|
||||
console.error(`[terminal-ime] started ibus-daemon PID ${ibusProcess.pid}`)
|
||||
await waitForHangulEngine(ibusProcess)
|
||||
}
|
||||
evidence.ibusDaemonPid = ibusProcess.pid
|
||||
console.error(`[terminal-ime] started ibus-daemon PID ${ibusProcess.pid}`)
|
||||
await waitForHangulEngine(ibusProcess)
|
||||
console.error(`[terminal-ime] IBus version: ${commandOutput('ibus', ['version'])}`)
|
||||
console.error(`[terminal-ime] IBus engine: ${commandOutput('ibus', ['engine'])}`)
|
||||
console.error(
|
||||
@@ -189,23 +231,49 @@ async function runInsideSession(evidenceDir) {
|
||||
'hangul-keyboard'
|
||||
])}`
|
||||
)
|
||||
evidence.ibusGroupBeforeCleanup = processGroupMembers(ibusProcess.pid)
|
||||
evidence.ibusGroupBeforeCleanup = ibusProcess?.pid ? processGroupMembers(ibusProcess.pid) : []
|
||||
console.error(`[terminal-ime] owned IBus group: ${evidence.ibusGroupBeforeCleanup.join('; ')}`)
|
||||
|
||||
const testProcess = spawn(
|
||||
process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm',
|
||||
[
|
||||
'run',
|
||||
'test:e2e:headful',
|
||||
'--workers=1',
|
||||
'--',
|
||||
'tests/e2e/terminal-ibus-hangul-native.spec.ts',
|
||||
'tests/e2e/terminal-hangul-terminating-digit-native.spec.ts'
|
||||
],
|
||||
nestedWayland
|
||||
? [
|
||||
'exec',
|
||||
'playwright',
|
||||
'test',
|
||||
'--config',
|
||||
'tests/playwright.config.ts',
|
||||
'tests/e2e/terminal-hangul-terminating-digit-native.spec.ts',
|
||||
'--project=electron-headful',
|
||||
'--workers=1',
|
||||
'--repeat-each=3',
|
||||
'--retries=0',
|
||||
'--reporter=list,json'
|
||||
]
|
||||
: [
|
||||
'run',
|
||||
'test:e2e:headful',
|
||||
'--workers=1',
|
||||
'--',
|
||||
'tests/e2e/terminal-ibus-hangul-native.spec.ts',
|
||||
'tests/e2e/terminal-hangul-terminating-digit-native.spec.ts'
|
||||
],
|
||||
{
|
||||
cwd: projectDir,
|
||||
env: {
|
||||
...process.env,
|
||||
...(nestedWayland
|
||||
? {
|
||||
ORCA_E2E_IME_INJECTOR: 'nested',
|
||||
ORCA_E2E_NESTED_FOCUS_CMD: path.join(
|
||||
projectDir,
|
||||
'config/scripts/focus-nested-wayland-terminal.sh'
|
||||
),
|
||||
ORCA_E2E_EXTRA_APP_ARGS:
|
||||
'--ozone-platform=wayland --enable-wayland-ime --wayland-text-input-version=3 --password-store=basic --use-mock-keychain --disable-gpu-sandbox',
|
||||
PLAYWRIGHT_JSON_OUTPUT_FILE: path.join(evidenceDir, 'playwright.json')
|
||||
}
|
||||
: {}),
|
||||
ORCA_E2E_FORWARD_APP_LOGS: '1',
|
||||
ORCA_E2E_NATIVE_IBUS_HANGUL: '1',
|
||||
[IME_ENGAGEMENT_RECEIPT_ENV]: receiptPath,
|
||||
@@ -232,6 +300,13 @@ async function runInsideSession(evidenceDir) {
|
||||
windowManagerProcess.pid
|
||||
)
|
||||
}
|
||||
if (nestedWayland && existsSync(path.join(evidenceDir, 'playwright.json'))) {
|
||||
mkdirSync(path.join(projectDir, 'test-results'), { recursive: true })
|
||||
copyFileSync(
|
||||
path.join(evidenceDir, 'playwright.json'),
|
||||
path.join(projectDir, 'test-results', 'terminal-wayland-playwright.json')
|
||||
)
|
||||
}
|
||||
closeSync(ibusLogFd)
|
||||
closeSync(windowManagerLogFd)
|
||||
mkdirSync(path.join(projectDir, 'test-results'), { recursive: true })
|
||||
@@ -241,7 +316,11 @@ async function runInsideSession(evidenceDir) {
|
||||
)
|
||||
copyFileSync(
|
||||
windowManagerLogPath,
|
||||
path.join(projectDir, 'test-results', 'terminal-ibus-hangul-native-xfwm4.log')
|
||||
path.join(
|
||||
projectDir,
|
||||
'test-results',
|
||||
nestedWayland ? 'terminal-wayland-gnome-shell.log' : 'terminal-ibus-hangul-native-xfwm4.log'
|
||||
)
|
||||
)
|
||||
writeFileSync(
|
||||
path.join(projectDir, 'test-results', 'terminal-ibus-hangul-native-processes.json'),
|
||||
@@ -269,6 +348,23 @@ async function runInsideSession(evidenceDir) {
|
||||
// Why unconditionally, and not only when Playwright failed: a skipped test reports as a pass,
|
||||
// so exit code 0 is exactly the state this check exists to distrust.
|
||||
const receiptText = existsSync(receiptPath) ? readFileSync(receiptPath, 'utf8') : ''
|
||||
if (nestedWayland) {
|
||||
verifyPlaywrightParticipation(
|
||||
JSON.parse(readFileSync(path.join(evidenceDir, 'playwright.json'), 'utf8')),
|
||||
{ titles: [waylandTitle], label: 'Native Wayland Hangul', repetitions: 3 }
|
||||
)
|
||||
const receipts = receiptText.trim().split('\n')
|
||||
if (receipts.length !== 3) {
|
||||
throw new Error('Expected three native Wayland engagement receipts')
|
||||
}
|
||||
for (const receipt of receipts) {
|
||||
const problems = verifyImeEngagementReceipts(receipt, [waylandTitle])
|
||||
if (problems.length) {
|
||||
throw new Error(problems.join('\n'))
|
||||
}
|
||||
}
|
||||
return testExitCode
|
||||
}
|
||||
const engagementProblems = verifyImeEngagementReceipts(receiptText, EXPECTED_NATIVE_IME_TESTS)
|
||||
if (engagementProblems.length > 0) {
|
||||
for (const problem of engagementProblems) {
|
||||
@@ -288,7 +384,7 @@ async function runInsideSession(evidenceDir) {
|
||||
|
||||
async function runOuter() {
|
||||
if (process.platform !== 'linux') {
|
||||
throw new Error('The native IBus Hangul E2E runner requires Linux/X11')
|
||||
throw new Error('The native IBus Hangul E2E runner requires Linux')
|
||||
}
|
||||
|
||||
const evidenceDir = mkdtempSync(path.join(os.tmpdir(), 'orca-terminal-ime-e2e-'))
|
||||
@@ -302,24 +398,36 @@ async function runOuter() {
|
||||
'xvfb-run',
|
||||
[
|
||||
'--auto-servernum',
|
||||
...(nestedWayland ? ['--server-args=-screen 0 1280x800x24'] : []),
|
||||
'dbus-run-session',
|
||||
'--',
|
||||
process.execPath,
|
||||
scriptPath,
|
||||
insideSessionFlag,
|
||||
evidenceDir
|
||||
evidenceDir,
|
||||
...(nestedWayland ? [nestedWaylandFlag] : [])
|
||||
],
|
||||
{
|
||||
cwd: projectDir,
|
||||
detached: true,
|
||||
env: {
|
||||
...process.env,
|
||||
...(nestedWayland
|
||||
? {
|
||||
WAYLAND_DISPLAY: 'wayland-orca-ime',
|
||||
XDG_SESSION_TYPE: 'wayland',
|
||||
XDG_CURRENT_DESKTOP: 'GNOME',
|
||||
LIBGL_ALWAYS_SOFTWARE: '1',
|
||||
NO_AT_BRIDGE: '1'
|
||||
}
|
||||
: {}),
|
||||
GTK_IM_MODULE: 'ibus',
|
||||
IBUS_ENABLE_SYNC_MODE: '1',
|
||||
LANG: process.env.LANG || 'C.UTF-8',
|
||||
QT_IM_MODULE: 'ibus',
|
||||
XDG_CACHE_HOME: path.join(evidenceDir, 'cache'),
|
||||
XDG_CONFIG_HOME: path.join(evidenceDir, 'config'),
|
||||
// GNOME 42 drops XDG_CONFIG_HOME when spawning IBus; both must use its default path.
|
||||
XDG_CACHE_HOME: nestedWayland ? undefined : path.join(evidenceDir, 'cache'),
|
||||
XDG_CONFIG_HOME: nestedWayland ? undefined : path.join(evidenceDir, 'config'),
|
||||
XDG_RUNTIME_DIR: runtimeDir,
|
||||
XMODIFIERS: '@im=ibus'
|
||||
},
|
||||
@@ -329,17 +437,20 @@ async function runOuter() {
|
||||
if (!sessionProcess.pid) {
|
||||
throw new Error('xvfb-run did not return a PID')
|
||||
}
|
||||
console.error(`[terminal-ime] started isolated X11 session PID ${sessionProcess.pid}`)
|
||||
console.error(`[terminal-ime] started isolated display session PID ${sessionProcess.pid}`)
|
||||
const exitCode = await waitForExit(sessionProcess)
|
||||
const remaining = await stopOwnedProcessGroup(sessionProcess.pid)
|
||||
if (remaining.length > 0) {
|
||||
throw new Error(`Owned X11 session processes survived cleanup: ${remaining.join('; ')}`)
|
||||
throw new Error(`Owned display session processes survived cleanup: ${remaining.join('; ')}`)
|
||||
}
|
||||
return exitCode
|
||||
}
|
||||
|
||||
const insideSession = process.argv[2] === insideSessionFlag
|
||||
try {
|
||||
if (nestedWayland && process.env.GITHUB_ACTIONS !== 'true') {
|
||||
throw new Error('Nested Wayland native input validation runs only in GitHub Actions')
|
||||
}
|
||||
if (insideSession && !process.argv[3]) {
|
||||
throw new Error(`${insideSessionFlag} requires an evidence directory argument`)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,21 @@ describe('terminal IME e2e workflow', () => {
|
||||
expect(runner).not.toContain('pkill')
|
||||
})
|
||||
|
||||
it('runs native Wayland independently with CJK fonts and retained evidence', () => {
|
||||
const job = workflow.jobs['linux-wayland']
|
||||
expect(job.needs).toBeUndefined()
|
||||
const install = job.steps.find((step) => step.run?.includes('apt-get install')).run
|
||||
for (const tool of ['gnome-shell', 'ibus-hangul', 'fonts-noto-cjk', 'xwininfo']) {
|
||||
expect(install).toContain(tool === 'xwininfo' ? 'x11-utils' : tool)
|
||||
}
|
||||
expect(job.steps.find((step) => step.run?.includes('--nested-wayland')).run).toBe(
|
||||
'node config/scripts/run-terminal-ibus-hangul-e2e.mjs --nested-wayland'
|
||||
)
|
||||
const upload = job.steps.find((step) => step.uses?.startsWith('actions/upload-artifact'))
|
||||
expect(upload.if).toBe('always()')
|
||||
expect(upload.with.name).toBe('terminal-wayland-ime-evidence')
|
||||
})
|
||||
|
||||
it('bounds blocking native input commands', () => {
|
||||
const nativeSpec = readFileSync(
|
||||
join(projectDir, 'tests/e2e/terminal-ibus-hangul-native.spec.ts'),
|
||||
|
||||
@@ -190,6 +190,8 @@ test.describe('Hangul terminating digit @headful', () => {
|
||||
}))
|
||||
console.log(`[digit-diag] ${JSON.stringify(launchDiagnostics)}`)
|
||||
if (INJECTOR === 'nested') {
|
||||
expect(launchDiagnostics.ozonePlatform).toBe('wayland')
|
||||
expect(launchDiagnostics.waylandDisplay).toBeTruthy()
|
||||
// Under Wayland the app's ready-to-show never fires here, so the window
|
||||
// stays hidden and the compositor has nothing to give keyboard focus to.
|
||||
await electronApp.evaluate(({ BrowserWindow }) => {
|
||||
|
||||
Reference in New Issue
Block a user