fix(ci): preserve portable fixtures and complete confirmation tab coverage
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
# Browser fixture portability and confirmation focus contracts
|
||||||
|
|
||||||
|
Run832 at main `d1fcd2308755dfc112a2bd6a736dd763bbc895f9` completed its331-case browser suite:314 passed,16 failed, one flaky Rules draft case. Deployment was skipped. The Playwright runtime-corruption signature did not recur in this completed run. These outcomes do not establish production readiness or resolve the existing intermittent Rules observation.
|
||||||
|
|
||||||
|
Eleven failures came from `seed-operation.py` calling Python3.12's `Path.is_junction` on the Debian runner's Python3.11.2 PosixPath. This was independently reproduced by running the unchanged helper in an isolated, network-disabled `node:20-bookworm` container on the runner host; host Python3.12.3 passed. Junction checks now execute only on Windows, where the existing Python3.12 guard is preserved. Absolute/direct-temp-root, symlink, hardlink, exact owner/schema and insert-only SQL protections remain unchanged. All29 self-checks pass both on local Windows and in that same Python3.11 container after the correction. No existing application data or production container is mounted into the test container.
|
||||||
|
|
||||||
|
Five failures were stale keyboard expectations after the verified shared confirmation scroll-body accessibility correction. The focusable named body now belongs in the native tab cycle. The tests explicitly verify Cancel → reverse Tab to named body → reverse Tab to Confirm → forward Tab to body → forward Tab to Cancel, followed by the original cancellation/focus/draft/action proofs. No focus assertion, axe rule, timeout or application behavior is relaxed. All five affected native cases pass with zero skips/flaky/retries,20October18:03:21–18:04:21UTC.
|
||||||
|
|
||||||
|
The bounded repair changes only the offline fixture and those two browser test consumers. Full release CI and deployed identity still require a new successful run. Production readiness remains NO-GO independently of the owner's authorization to publish main. The Qwen3.5 adapter preparation is separately tested; model activation and P5-13 qualification remain separate.
|
||||||
@@ -69,11 +69,11 @@ def seed_operation(payload: object) -> str:
|
|||||||
except OSError as error:
|
except OSError as error:
|
||||||
raise SeedDenied("The isolated dataRoot must already exist") from error
|
raise SeedDenied("The isolated dataRoot must already exist") from error
|
||||||
if (root != requested or root.parent != temporary or not root.is_dir()
|
if (root != requested or root.parent != temporary or not root.is_dir()
|
||||||
or requested.is_symlink() or requested.is_junction()
|
or requested.is_symlink() or (sys.platform == "win32" and requested.is_junction())
|
||||||
or not re.fullmatch(r"jobtracker-e2e-[A-Za-z0-9][A-Za-z0-9_-]*", root.name)):
|
or not re.fullmatch(r"jobtracker-e2e-[A-Za-z0-9][A-Za-z0-9_-]*", root.name)):
|
||||||
raise SeedDenied("Only an unlinked jobtracker-e2e-* direct child of system temp is allowed")
|
raise SeedDenied("Only an unlinked jobtracker-e2e-* direct child of system temp is allowed")
|
||||||
database = root / "jobtracker.db"
|
database = root / "jobtracker.db"
|
||||||
if (not database.is_file() or database.is_symlink() or database.is_junction()
|
if (not database.is_file() or database.is_symlink() or (sys.platform == "win32" and database.is_junction())
|
||||||
or database.resolve(strict=True) != database or database.stat().st_nlink != 1):
|
or database.resolve(strict=True) != database or database.stat().st_nlink != 1):
|
||||||
raise SeedDenied("The unlinked existing direct jobtracker.db file is required")
|
raise SeedDenied("The unlinked existing direct jobtracker.db file is required")
|
||||||
# EF's SQLite Guid converter writes uppercase canonical text.
|
# EF's SQLite Guid converter writes uppercase canonical text.
|
||||||
@@ -110,7 +110,7 @@ def temporary_fixture():
|
|||||||
yield root
|
yield root
|
||||||
finally:
|
finally:
|
||||||
assert root.parent == Path(tempfile.gettempdir()).resolve()
|
assert root.parent == Path(tempfile.gettempdir()).resolve()
|
||||||
assert root.name.startswith("jobtracker-e2e-operation-selftest-") and not root.is_symlink() and not root.is_junction()
|
assert root.name.startswith("jobtracker-e2e-operation-selftest-") and not root.is_symlink() and not (sys.platform == "win32" and root.is_junction())
|
||||||
|
|
||||||
|
|
||||||
def self_test() -> dict:
|
def self_test() -> dict:
|
||||||
|
|||||||
@@ -171,8 +171,13 @@ for (const [mode, language, width] of [["light", "en", 1440], ["dark", "nb", 144
|
|||||||
await expect(cancel).toBeFocused();
|
await expect(cancel).toBeFocused();
|
||||||
await expect(confirm).toHaveAccessibleDescription(t.attachmentsDeleteConfirm.replace("{name}", "Reviewed æøå.txt"));
|
await expect(confirm).toHaveAccessibleDescription(t.attachmentsDeleteConfirm.replace("{name}", "Reviewed æøå.txt"));
|
||||||
await cancel.press("Shift+Tab");
|
await cancel.press("Shift+Tab");
|
||||||
|
const messageRegion = confirm.getByRole("region", { name: t.attachmentsDeleteTitle, exact: true });
|
||||||
|
await expect(messageRegion).toBeFocused();
|
||||||
|
await messageRegion.press("Shift+Tab");
|
||||||
await expect(confirm.getByRole("button", { name: t.attachmentsDelete, exact: true })).toBeFocused();
|
await expect(confirm.getByRole("button", { name: t.attachmentsDelete, exact: true })).toBeFocused();
|
||||||
await page.keyboard.press("Tab");
|
await page.keyboard.press("Tab");
|
||||||
|
await expect(messageRegion).toBeFocused();
|
||||||
|
await page.keyboard.press("Tab");
|
||||||
await expect(cancel).toBeFocused();
|
await expect(cancel).toBeFocused();
|
||||||
await scan(page, info, "confirm", true);
|
await scan(page, info, "confirm", true);
|
||||||
await capture(page, info, `confirm-${suffix}`);
|
await capture(page, info, `confirm-${suffix}`);
|
||||||
|
|||||||
@@ -366,7 +366,11 @@ test('[P7-05 Discovery] keyboard native create and alert updates persist; delete
|
|||||||
const cancel = dialog.getByRole('button', { name: t.cancel, exact: true });
|
const cancel = dialog.getByRole('button', { name: t.cancel, exact: true });
|
||||||
await expect(cancel).toBeFocused();
|
await expect(cancel).toBeFocused();
|
||||||
await page.keyboard.press('Shift+Tab');
|
await page.keyboard.press('Shift+Tab');
|
||||||
|
const messageRegion = dialog.getByRole('region', { name: await dialog.getByRole('heading').innerText(), exact: true });
|
||||||
|
await expect(messageRegion).toBeFocused();
|
||||||
|
await messageRegion.press('Shift+Tab');
|
||||||
await expect(dialog.getByRole('button', { name: t.adminUsersDelete, exact: true })).toBeFocused();
|
await expect(dialog.getByRole('button', { name: t.adminUsersDelete, exact: true })).toBeFocused();
|
||||||
|
await page.keyboard.press('Tab'); await expect(messageRegion).toBeFocused();
|
||||||
await page.keyboard.press('Tab'); await expect(cancel).toBeFocused();
|
await page.keyboard.press('Tab'); await expect(cancel).toBeFocused();
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await expect(dialog).toHaveCount(0); await expect(deleteButton).toBeFocused();
|
await expect(dialog).toHaveCount(0); await expect(deleteButton).toBeFocused();
|
||||||
|
|||||||
Reference in New Issue
Block a user