Skip to content

Commit 50e5259

Browse files
author
DavidQ
committed
Add deeper Playwright coverage for Preview Generator V2 UI and behavior - PR_26126_044-preview-generator-v2-playwright-depth
1 parent bd7b736 commit 50e5259

3 files changed

Lines changed: 291 additions & 3 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
PR_26126_044 Preview Generator V2 Playwright depth
2+
3+
Scope
4+
- Extended tests/playwright/PreviewGeneratorV2Baseline.spec.mjs.
5+
- Added one minimal Preview Generator V2 CSS fix because the deeper accordion test found a real right-column collapse bug.
6+
- No samples, schemas, or start_of_day files were modified.
7+
8+
Playwright coverage added
9+
- Generate Preview required-field state:
10+
- disabled before a repo destination is selected.
11+
- remains disabled when only Paths or IDs is filled.
12+
- becomes enabled after a repo destination is selected and required existing defaults remain valid.
13+
- Target Source default:
14+
- verifies Games is selected by default.
15+
- Accordion behavior:
16+
- verifies close/open state for left-column sections:
17+
- Repo Destination
18+
- Target Source
19+
- Asset folder
20+
- Capture mode
21+
- Render Controls
22+
- verifies close/open state for right-column Output Summary.
23+
- Paths or IDs input:
24+
- verifies the input accepts text.
25+
- verifies it remains visible.
26+
- verifies it uses the expected stretch-related styling and width inside its accordion content.
27+
- Status log:
28+
- verifies Clear empties the status log output area.
29+
- Phase input behavior:
30+
- uses a browser-side fake File System Access repo handle.
31+
- verifies samples/phase-01 is resolved by directory enumeration.
32+
- verifies only folders with index.html are processed.
33+
- verifies missing numeric folders and file entries are not processed.
34+
- verifies preview.svg write targets the enumerated sample folder only.
35+
36+
Bug found and fixed
37+
- Output Summary content received the hidden attribute but stayed visually visible because a Preview Generator V2 CSS rule overrode the shared hidden rule.
38+
- Added a scoped hidden override:
39+
.preview-generator-v2__output-summary .accordion-v2__content[hidden] { display: none; }
40+
- This preserves intended accordion behavior and does not alter generation logic.
41+
42+
Validation
43+
- node --check tests/playwright/PreviewGeneratorV2Baseline.spec.mjs passed.
44+
- npm run test:workspace-v2 passed.
45+
- Result: 3 passed.
46+
- Confirmed no samples, schemas, or start_of_day changes.
47+
48+
Full samples smoke test
49+
- Skipped intentionally.
50+
- Reason: this PR adds targeted Preview Generator V2 Playwright coverage and does not modify sample JSON or the shared sample runtime.
51+
52+
Manual validation
53+
- Open tools/preview-generator-v2/index.html.
54+
- Confirm Generate Preview is disabled until required fields are present.
55+
- Toggle left-column accordions and Output Summary.
56+
- Enter text into Paths or IDs and confirm it remains visible in the center panel.
57+
- Use Clear in Status and confirm the log output clears.

tests/playwright/PreviewGeneratorV2Baseline.spec.mjs

Lines changed: 230 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,139 @@
11
import { expect, test } from "@playwright/test";
22
import { startRepoServer } from "../helpers/playwrightRepoServer.mjs";
33

4+
async function installFakeRepoPicker(page, { validSampleIds = ["0102"], invalidSampleIds = [] } = {}) {
5+
await page.addInitScript(({ validSampleIds: validIds, invalidSampleIds: invalidIds }) => {
6+
class FakeFileHandle {
7+
constructor(name, path, contents = "") {
8+
this.kind = "file";
9+
this.name = name;
10+
this.path = path;
11+
this.contents = contents;
12+
}
13+
14+
async getFile() {
15+
const handle = this;
16+
return {
17+
async text() {
18+
return handle.contents;
19+
}
20+
};
21+
}
22+
23+
async createWritable() {
24+
const handle = this;
25+
return {
26+
async write(contents) {
27+
handle.contents = String(contents);
28+
window.__previewGeneratorV2Writes.push({ path: handle.path, contents: handle.contents });
29+
},
30+
async close() {}
31+
};
32+
}
33+
}
34+
35+
class FakeDirectoryHandle {
36+
constructor(name, path = name) {
37+
this.kind = "directory";
38+
this.name = name;
39+
this.path = path;
40+
this.children = new Map();
41+
}
42+
43+
addDirectory(name) {
44+
const directory = new FakeDirectoryHandle(name, `${this.path}/${name}`);
45+
this.children.set(name, directory);
46+
return directory;
47+
}
48+
49+
addFile(name, contents = "") {
50+
const file = new FakeFileHandle(name, `${this.path}/${name}`, contents);
51+
this.children.set(name, file);
52+
return file;
53+
}
54+
55+
async getDirectoryHandle(name, options = {}) {
56+
const child = this.children.get(name);
57+
if (child?.kind === "directory") {
58+
return child;
59+
}
60+
if (options.create) {
61+
return this.addDirectory(name);
62+
}
63+
throw new Error(`Missing directory: ${this.path}/${name}`);
64+
}
65+
66+
async getFileHandle(name, options = {}) {
67+
const child = this.children.get(name);
68+
if (child?.kind === "file") {
69+
return child;
70+
}
71+
if (options.create) {
72+
return this.addFile(name);
73+
}
74+
throw new Error(`Missing file: ${this.path}/${name}`);
75+
}
76+
77+
async *entries() {
78+
for (const entry of this.children.entries()) {
79+
yield entry;
80+
}
81+
}
82+
}
83+
84+
const repo = new FakeDirectoryHandle("HTML-JavaScript-Gaming");
85+
const samples = repo.addDirectory("samples");
86+
const phase01 = samples.addDirectory("phase-01");
87+
repo.addDirectory("games");
88+
repo.addDirectory("tools");
89+
90+
for (const sampleId of validIds) {
91+
phase01.addDirectory(sampleId).addFile("index.html", "<!doctype html><canvas></canvas>");
92+
}
93+
for (const sampleId of invalidIds) {
94+
phase01.addDirectory(sampleId);
95+
}
96+
phase01.addFile("0199", "not a sample folder");
97+
98+
window.__previewGeneratorV2Writes = [];
99+
window.showDirectoryPicker = async () => repo;
100+
}, { validSampleIds, invalidSampleIds });
101+
}
102+
103+
async function openPreviewGenerator(page, { withFakeRepo = false, validSampleIds, invalidSampleIds } = {}) {
104+
const server = await startRepoServer();
105+
if (withFakeRepo) {
106+
await installFakeRepoPicker(page, { validSampleIds, invalidSampleIds });
107+
}
108+
await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { waitUntil: "domcontentloaded" });
109+
return server;
110+
}
111+
112+
async function expectAccordionToggles(page, contentId) {
113+
const header = page.locator(`.accordion-v2__header[aria-controls="${contentId}"]`);
114+
const content = page.locator(`#${contentId}`);
115+
await expect(header).toBeVisible();
116+
await expect(content).toBeVisible();
117+
118+
await header.click();
119+
await expect(content).toBeHidden();
120+
await expect(header).toHaveAttribute("aria-expanded", "false");
121+
122+
await header.click();
123+
await expect(content).toBeVisible();
124+
await expect(header).toHaveAttribute("aria-expanded", "true");
125+
}
126+
4127
test.describe("Preview Generator V2 baseline", () => {
5128
test("launches the tool shell and toggles a working accordion", async ({ page }) => {
6-
const server = await startRepoServer();
129+
const server = await openPreviewGenerator(page);
7130
const pageErrors = [];
8131

9132
page.on("pageerror", (error) => {
10133
pageErrors.push(error.message);
11134
});
12135

13136
try {
14-
await page.goto(`${server.baseUrl}/tools/preview-generator-v2/index.html`, { waitUntil: "domcontentloaded" });
15-
16137
await expect(page.locator(".preview-generator-v2.app-shell")).toBeVisible();
17138
await expect(page.locator('link[href="../common/toolShellCommon.css"]')).toHaveCount(1);
18139
await expect(page.locator("h1", { hasText: "Preview Generator V2" })).toBeVisible();
@@ -79,4 +200,110 @@ test.describe("Preview Generator V2 baseline", () => {
79200
await server.close();
80201
}
81202
});
203+
204+
test("exercises controls, required-field gating, accordions, paths layout, and status clear", async ({ page }) => {
205+
const server = await openPreviewGenerator(page, { withFakeRepo: true });
206+
const pageErrors = [];
207+
208+
page.on("pageerror", (error) => {
209+
pageErrors.push(error.message);
210+
});
211+
212+
try {
213+
await expect(page.locator("#targetTypeGames")).toBeChecked();
214+
await expect(page.locator("#executeBtn")).toBeDisabled();
215+
216+
await page.locator("#sampleList").fill("my-game");
217+
await expect(page.locator("#sampleList")).toHaveValue("my-game");
218+
await expect(page.locator("#executeBtn")).toBeDisabled();
219+
220+
const pathsLayout = await page.locator("#sampleList").evaluate((input) => {
221+
const content = document.getElementById("pathsOrIdsContent");
222+
const inputRect = input.getBoundingClientRect();
223+
const contentRect = content.getBoundingClientRect();
224+
const inputStyle = getComputedStyle(input);
225+
const contentStyle = getComputedStyle(content);
226+
return {
227+
inputHeight: inputRect.height,
228+
contentHeight: contentRect.height,
229+
inputWidth: inputRect.width,
230+
contentWidth: contentRect.width,
231+
display: inputStyle.display,
232+
height: inputStyle.height,
233+
overflow: inputStyle.overflow,
234+
resize: inputStyle.resize,
235+
contentDisplay: contentStyle.display
236+
};
237+
});
238+
expect(pathsLayout.display).not.toBe("none");
239+
expect(pathsLayout.contentDisplay).toBe("flex");
240+
expect(pathsLayout.inputHeight).toBeGreaterThan(0);
241+
expect(pathsLayout.height).not.toBe("auto");
242+
expect(pathsLayout.overflow).toBe("auto");
243+
expect(pathsLayout.resize).toBe("none");
244+
expect(pathsLayout.contentHeight - pathsLayout.inputHeight).toBeLessThan(40);
245+
expect(pathsLayout.contentWidth - pathsLayout.inputWidth).toBeLessThan(40);
246+
247+
await page.locator("#pickRepoBtn").click();
248+
await expect(page.locator("#repoSelectedValue")).toHaveText("HTML-JavaScript-Gaming");
249+
await expect(page.locator("#executeBtn")).toBeEnabled();
250+
251+
await page.locator("#clearLogBtn").click();
252+
await expect(page.locator("#log")).toHaveText("");
253+
254+
for (const contentId of [
255+
"repoDestinationContent",
256+
"targetSourceContent",
257+
"assetFolderContent",
258+
"captureModeContent",
259+
"renderControlsContent",
260+
"outputSummaryContent"
261+
]) {
262+
await expectAccordionToggles(page, contentId);
263+
}
264+
265+
expect(pageErrors).toEqual([]);
266+
} finally {
267+
await server.close();
268+
}
269+
});
270+
271+
test("phase folder input enumerates existing sample folders only", async ({ page }) => {
272+
const server = await openPreviewGenerator(page, {
273+
withFakeRepo: true,
274+
validSampleIds: ["0102"],
275+
invalidSampleIds: ["0101", "0103"]
276+
});
277+
const pageErrors = [];
278+
279+
page.on("pageerror", (error) => {
280+
pageErrors.push(error.message);
281+
});
282+
283+
try {
284+
await page.locator("#pickRepoBtn").click();
285+
await page.locator("#targetTypeSamples").check();
286+
await page.locator("#sampleList").fill("samples/phase-01");
287+
288+
await expect(page.locator("#writeFolderActualValue")).toHaveText("samples\\phase-01\\0102\\assets\\images");
289+
await expect(page.locator("#executeBtn")).toBeEnabled();
290+
291+
await page.locator("#executeBtn").click();
292+
await expect(page.locator("#log")).toContainText("RUN 0102", { timeout: 10000 });
293+
await expect(page.locator("#log")).toContainText("Done.", { timeout: 10000 });
294+
295+
const logText = await page.locator("#log").textContent();
296+
expect(logText).not.toContain("RUN 0101");
297+
expect(logText).not.toContain("RUN 0103");
298+
expect(logText).not.toContain("RUN 0199");
299+
300+
const writes = await page.evaluate(() => window.__previewGeneratorV2Writes);
301+
expect(writes).toHaveLength(1);
302+
expect(writes[0].path).toBe("HTML-JavaScript-Gaming/samples/phase-01/0102/assets/images/preview.svg");
303+
304+
expect(pageErrors).toEqual([]);
305+
} finally {
306+
await server.close();
307+
}
308+
});
82309
});

tools/preview-generator-v2/previewGeneratorV2.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ body.tools-platform-tool-page[data-tool-id="preview-generator-v2"] > .palette-ma
5050
padding: 8px;
5151
}
5252

53+
.preview-generator-v2__output-summary .accordion-v2__content[hidden] {
54+
display: none;
55+
}
56+
5357
.preview-generator-v2__output-summary .accordion-v2__header {
5458
flex-basis: 38px;
5559
min-height: 38px;

0 commit comments

Comments
 (0)