Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1946130
test(e2e): parse the fixture once and clone it per test
Rl0007 Oct 6, 2026
e793d44
chore(e2e): drop the duplicate playwright reference and run workflow …
Rl0007 Oct 6, 2026
72b48e8
test(e2e): use a 98-page openstax chapter as the fixture
Rl0007 Oct 6, 2026
ef9d4d5
test(e2e): let E2E_FIXTURE_PDF swap in a small fixture for local runs
Rl0007 Oct 6, 2026
8b0b33a
test(e2e): read fixture titles from the seeded outline in crop, agent…
Rl0007 Oct 6, 2026
046917f
test(e2e): type the import log rows in the parse spec
Rl0007 Oct 6, 2026
9170bc6
test(e2e): mark known failures at the failing assertion so flakes sti…
Rl0007 Oct 6, 2026
f04be24
test(e2e): run the tests for bugs fixed on main as normal tests
Rl0007 Oct 6, 2026
a7ae548
test(e2e): crop only figure pages one section owns in F-CROP-04
Rl0007 Oct 6, 2026
295e8fa
test(e2e): check the delete confirm card names the section
Rl0007 Oct 6, 2026
d922b12
fix(e2e): start the ci worker without CI so its jobs can use local mo…
Rl0007 Oct 6, 2026
09b7b51
ci(e2e): give the run 300 minutes for the 98-page remediation
Rl0007 Oct 6, 2026
49cb55a
test(e2e): match the Actions button exactly and wait for the ask delete
Rl0007 Oct 6, 2026
c53d327
test(e2e): open leaf sections in the published wiki and find figures …
Rl0007 Oct 6, 2026
fffdbf6
test(e2e): pick section types, figures and spans that the 98-page fix…
Rl0007 Oct 6, 2026
0fc7ff2
test(e2e): crop a figure off the leaf pages in the publish setup
Rl0007 Oct 6, 2026
7744e88
ci(e2e): save the site's error log with the bench logs
Rl0007 Oct 6, 2026
eeeaa98
fix(e2e): delete test imports only in the teardown so import names ar…
Rl0007 Oct 7, 2026
fd19825
Merge remote-tracking branch 'upstream/main' into feat/e2e-parse-once
Rl0007 Oct 7, 2026
4b2c44d
fix(e2e): leave the W99 projects to the teardown like the other parse…
Rl0007 Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/e2e-from-prs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ or code the e2e user cannot reach. Also skip when an existing test already asser

## 3. Write at most 3 tests

1. Read `references/playwright.md` (next to this file), every file in `e2e/helpers/`, and the whole
target spec before you write anything.
1. Read every file in `e2e/helpers/` and the whole target spec before you write anything.
2. Pick the spec by area: `agent`, `ask`, `tree` (sections), `crop` (figures, pages), `publish`
(wiki), `upload`, `parse` (import, sectionize, jobs). Never add to `smoke.spec.ts`. Create
`e2e/specs/<area>.spec.ts` only if no area fits.
Expand All @@ -50,6 +49,7 @@ Rules:
- Data you create is titled `` `${PREFIX} <area> W<n> ${Date.now()}` `` and deleted in `afterAll`
(`deleteByPrefix`, `deleteTestProjects`, `deleteImport`). Never delete `[fixture]` records. A test
that changes a fixture restores it in `afterEach` (see `restoreTree` in `tree.spec.ts`).
- Make test data in code (`helpers/pdf.ts`, API calls); never add files.
- Wait on database state with `waitForValue` or `waitFor` through `api`. Never wait on UI progress
text, and never use `page.waitForTimeout`.
- `@llm` tests: `test.describe.configure({ retries: 1 })`, assert records through `api`, never the
Expand Down
126 changes: 0 additions & 126 deletions .claude/skills/e2e-from-prs/references/playwright.md

This file was deleted.

18 changes: 16 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ on:
- .github/workflows/e2e.yml
- .github/actions/e2e-site/**

defaults:
run:
shell: bash

jobs:
playwright:
name: Playwright
runs-on: ubuntu-latest
timeout-minutes: 240
timeout-minutes: 300
permissions:
contents: read
issues: write
Expand Down Expand Up @@ -95,6 +99,14 @@ jobs:
free -m
echo "Peak used memory (MB): $(sort -n ~/memory-used.log | tail -n 1)"

- name: Save the Error Log
if: always()
working-directory: /home/runner/frappe-bench
run: |
bench --site wikify.localhost execute frappe.get_all \
--kwargs "{'doctype': 'Error Log', 'fields': ['creation', 'method', 'error'], 'order_by': 'creation desc', 'limit_page_length': 50}" \
> logs/e2e-error-log.json 2>&1 || true

- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v7
Expand All @@ -110,7 +122,9 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: bench-logs
path: /home/runner/frappe-bench/logs/*.log
path: |
/home/runner/frappe-bench/logs/*.log
/home/runner/frappe-bench/logs/e2e-error-log.json
if-no-files-found: ignore

- name: Report a failed weekly run
Expand Down
1 change: 1 addition & 0 deletions frontend/e2e/.env.e2e.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ E2E_USER=e2e@example.com
E2E_PASSWORD=
E2E_API_KEY=
E2E_API_SECRET=
# E2E_FIXTURE_PDF=../docs/what-is-wikify.pdf # small doc for local runs until the bench has the 6 h parse timeout
1 change: 1 addition & 0 deletions frontend/e2e/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adapted from *Anatomy and Physiology*, J. Gordon Betts et al., OpenStax (2013), pp. 1155–1252, CC BY 4.0, https://openstax.org/details/books/anatomy-and-physiology. Changes: page range extracted; three University of Michigan micrographs (CC BY-NC-SA) removed.
Binary file not shown.
26 changes: 24 additions & 2 deletions frontend/e2e/helpers/cleanup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Api } from "./api";
import { waitFor } from "./wait";
import { renumberTree } from "./wikify";

const BUSY = ["Queued", "Parsing", "Remediating", "Generating Wiki"];

Expand Down Expand Up @@ -51,17 +52,32 @@ export async function waitForIdle(
).catch((error) => console.warn(`cleanup: ${error.message}`));
}

// A REST delete runs NestedSet's table-wide lft shift, which reorders other documents' trees
// (the [fixture] source among them). delete_section deletes raw and renumbers only this document.
async function deleteSections(api: Api, sourceDocument: string): Promise<void> {
const filters = { source_document: sourceDocument, parent_source_section: ["is", "not set"] };
const roots = await api.getList("Source Section", { filters });
if (roots.length) await renumberTree(api, sourceDocument);
for (const root of roots) {
await api
.call("wikify.api.sections.delete_section", { name: root.name })
.catch((error) => console.warn(`cleanup: delete_section ${root.name}: ${error.message}`));
}
await deleteTree(api, "Source Section", { source_document: sourceDocument });
}

export async function deleteImport(api: Api, importName: string): Promise<void> {
await waitForIdle(api, importName);
const imp = await api.getValue("Wikify Import", importName, ["source_document"]);
const sourceDocument = imp?.source_document;
await deleteByFilter(api, "Import Log Entry", { import: importName });
if (sourceDocument) {
// Unlinked first: delete_section refuses a document whose import is Completed.
await api.setValue("Wikify Import", importName, { source_document: null });
await deleteByFilter(api, "Wikify Agent Session", { source_document: sourceDocument });
await deleteTree(api, "Source Section", { source_document: sourceDocument });
await deleteSections(api, sourceDocument);
await deleteByFilter(api, "Section Reference", { source_document: sourceDocument });
await deleteByFilter(api, "Source Page", { source_document: sourceDocument });
await api.setValue("Wikify Import", importName, { source_document: null });
await api.setValue("Source Document", sourceDocument, {
wiki_root_group: null,
wiki_space: null,
Expand All @@ -85,7 +101,13 @@ export async function deleteWikiSpace(api: Api, wikiSpace: string): Promise<void
await tryDelete(api, "Wiki Space", wikiSpace);
}

// Runs once, in the teardown. Deleting imports mid-run reverts the IMP- naming series, so a new
// import reuses a deleted one's name and job id, and its parse job never runs.
export async function deleteTestProjects(api: Api, prefix: string): Promise<void> {
const strays = await api.getList("Wikify Import", {
filters: { import_title: ["like", `${prefix}%`] },
});
for (const imp of strays) await deleteImport(api, imp.name);
const projects = await api.getList("Wikify Project", {
filters: { project_name: ["like", `${prefix}%`] },
});
Expand Down
11 changes: 10 additions & 1 deletion frontend/e2e/helpers/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,13 @@ export const PREFIX = "[test]";
export const FIXTURE_PREFIX = "[fixture]";

export const STATE_DIR = path.resolve(import.meta.dirname, "../.state");
export const FIXTURE_PDF = path.resolve(import.meta.dirname, "../../../docs/what-is-wikify.pdf");
export const WHAT_IS_WIKIFY_PDF = path.resolve(
import.meta.dirname,
"../../../docs/what-is-wikify.pdf",
);
export const FIXTURE_PDF_OVERRIDDEN = !!process.env.E2E_FIXTURE_PDF;
export const FIXTURE_PDF = FIXTURE_PDF_OVERRIDDEN
? path.resolve(import.meta.dirname, "../..", process.env.E2E_FIXTURE_PDF!)
: path.resolve(import.meta.dirname, "../fixtures/openstax-anatomy-ch24-25.pdf");
export const FIXTURE_PDF_SHA256 =
"62d650cf40e02faea4395dc2301891c9e5165f88630713d4d92b7f704d0d6fcf";
12 changes: 11 additions & 1 deletion frontend/e2e/helpers/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,21 @@ import { test as base } from "@playwright/test";
import { Api } from "./api";
import { STATE_DIR } from "./env";

export type ImportFixture = { import: string; sourceDocument: string; title: string };
export type OutlineEntry = { name: string; title: string; page_start: number; page_end: number };

export type FixtureOutline = { root: OutlineEntry | null; children: OutlineEntry[] };

export type ImportFixture = {
import: string;
sourceDocument: string;
title: string;
outline: FixtureOutline;
};

export type Fixtures = {
project: string;
projectName: string;
source: ImportFixture;
review: ImportFixture;
agent: ImportFixture;
};
Expand Down
Loading
Loading