Skip to content

Commit 27a0442

Browse files
author
DavidQ
committed
Add Playwright UI test for Workspace V2 Asset Manager launch and add/remove flow - PR_11_318B
1 parent 0feea9b commit 27a0442

8 files changed

Lines changed: 205 additions & 5 deletions

File tree

docs/dev/codex_commands.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,10 @@ PR_11_318A
180180
```bash
181181
npx @openai/codex run --model gpt-5.3-codex --reasoning medium "Implement PR_11_318A: Remove test-to-test imports by extracting RuntimeSceneLoaderHotReload shared logic into tests/helpers and updating final/tool tests to consume helper."
182182
```
183+
184+
---
185+
PR_11_318B
186+
187+
```bash
188+
npx @openai/codex run --model gpt-5.3-codex --reasoning medium "Implement PR_11_318B: Add first Playwright UI test for Workspace V2 launching Asset Manager V2, add/remove asset flow, and export verification."
189+
```

docs/dev/commit_comment.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Decouple Runtime Scene Loader shared logic into tests helper and remove test-to-test imports - PR 11.318A
1+
Add first Playwright Workspace V2 to Asset Manager V2 add/remove export verification UI test - PR 11.318B
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# PR_11_318B Report
2+
3+
## Purpose
4+
Add first browser UI coverage for Workspace V2 launching Asset Manager V2 and validating asset add/remove behavior with export verification.
5+
6+
## Files Changed
7+
- `tests/ui/workspace-v2.asset-manager.spec.js`
8+
- `docs/pr/PR_11_318B_PLAYWRIGHT_WORKSPACE_ASSET_MANAGER/PLAN_PR.md`
9+
- `docs/pr/PR_11_318B_PLAYWRIGHT_WORKSPACE_ASSET_MANAGER/BUILD_PR.md`
10+
- `docs/dev/reports/PR_11_318B_report.md`
11+
- `docs/dev/codex_commands.md`
12+
- `docs/dev/commit_comment.txt`
13+
14+
## Test Coverage Added
15+
- Workspace V2 open/reset
16+
- Producer select/load fixture/create+launch
17+
- Asset Manager V2 loaded state and fixture entry visibility (`Player Ship`)
18+
- Add new asset and visibility assertion (`Enemy Ship`)
19+
- Remove added asset and disappearance assertion
20+
- Return to Workspace V2
21+
- Export workspace manifest and verify active session catalog entry ids:
22+
- includes `asset-001`
23+
- excludes `asset-002`
24+
25+
## Validation Commands
26+
- `node --check tests/ui/workspace-v2.asset-manager.spec.js` -> **PASS**
27+
- `npx playwright test tests/ui/workspace-v2.asset-manager.spec.js` -> **PASS**
28+
29+
## Full Samples Smoke Decision
30+
- **Skipped** full samples smoke test.
31+
- Reason: this PR adds targeted UI automation only and does not touch shared sample framework/runtime contracts.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# BUILD_PR_11_318B
2+
3+
## Implementation
4+
- Added Playwright UI test:
5+
- `tests/ui/workspace-v2.asset-manager.spec.js`
6+
- Test covers required flow:
7+
1. open Workspace V2
8+
2. click Full Reset
9+
3. select `Asset Manager V2` in Producer
10+
4. click Load Fixture
11+
5. click Create Session + Launch
12+
6. assert Asset Manager V2 loads and `Player Ship` appears
13+
7. add asset (`asset-002`, `Enemy Ship`, `svg`, `assets/vectors/enemy-ship.svg`)
14+
8. assert `Enemy Ship` appears
15+
9. remove `asset-002`
16+
10. assert `Enemy Ship` disappears
17+
11. return to Workspace V2
18+
12. export workspace manifest and assert entries include `asset-001` and exclude `asset-002`
19+
20+
## Notes
21+
- No runtime feature changes.
22+
- No schema changes.
23+
- No Workspace V2 contract changes.
24+
- Uses a small local static HTTP server inside the test for stable fixture fetch and navigation.
25+
26+
## Validation
27+
- `node --check tests/ui/workspace-v2.asset-manager.spec.js`
28+
- `npx playwright test tests/ui/workspace-v2.asset-manager.spec.js`
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# PLAN_PR_11_318B
2+
3+
## Purpose
4+
Add the first Playwright browser UI test that validates Workspace V2 launching Asset Manager V2 and performing add/remove asset behavior with export verification.
5+
6+
## Scope
7+
- `tests/ui/workspace-v2.asset-manager.spec.js`
8+
- `docs/dev/reports/PR_11_318B_report.md`
9+
- `docs/dev/codex_commands.md`
10+
- `docs/dev/commit_comment.txt`
11+
12+
## Steps
13+
1. Add a single Playwright spec for Workspace V2 → Asset Manager V2 flow.
14+
2. Use accessible selectors and existing ids.
15+
3. Perform add/remove asset assertions in Asset Manager V2.
16+
4. Return to Workspace V2 and assert exported manifest contains `asset-001` and excludes `asset-002`.
17+
5. Run targeted syntax check + targeted Playwright spec only.

playwright.config.cjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
use: {
3+
headless: false,
4+
launchOptions: {
5+
slowMo: 500
6+
}
7+
}
8+
};

test-results/.last-run.json

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { test, expect } from "@playwright/test";
2+
import fs from "node:fs/promises";
3+
import path from "node:path";
4+
import http from "node:http";
5+
import { fileURLToPath } from "node:url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const repoRoot = path.resolve(__dirname, "..", "..");
10+
11+
function contentTypeForPath(filePath) {
12+
const extension = path.extname(filePath).toLowerCase();
13+
if (extension === ".html") return "text/html; charset=utf-8";
14+
if (extension === ".js" || extension === ".mjs") return "text/javascript; charset=utf-8";
15+
if (extension === ".json") return "application/json; charset=utf-8";
16+
if (extension === ".css") return "text/css; charset=utf-8";
17+
if (extension === ".svg") return "image/svg+xml";
18+
return "application/octet-stream";
19+
}
20+
21+
async function startRepoServer() {
22+
const server = http.createServer(async (request, response) => {
23+
try {
24+
const requestUrl = new URL(request.url || "/", "http://127.0.0.1");
25+
const decodedPath = decodeURIComponent(requestUrl.pathname);
26+
const normalizedPath = path.normalize(decodedPath).replace(/^(\.\.[/\\])+/, "");
27+
const absolutePath = path.resolve(repoRoot, `.${normalizedPath}`);
28+
if (!absolutePath.startsWith(repoRoot)) {
29+
response.statusCode = 403;
30+
response.end("Forbidden");
31+
return;
32+
}
33+
let targetPath = absolutePath;
34+
const stat = await fs.stat(targetPath).catch(() => null);
35+
if (stat && stat.isDirectory()) {
36+
targetPath = path.join(targetPath, "index.html");
37+
}
38+
const fileContents = await fs.readFile(targetPath);
39+
response.statusCode = 200;
40+
response.setHeader("Content-Type", contentTypeForPath(targetPath));
41+
response.end(fileContents);
42+
} catch {
43+
response.statusCode = 404;
44+
response.end("Not Found");
45+
}
46+
});
47+
await new Promise((resolve, reject) => {
48+
server.listen(0, "127.0.0.1", () => resolve());
49+
server.on("error", reject);
50+
});
51+
const address = server.address();
52+
if (!address || typeof address === "string") {
53+
throw new Error("Failed to start UI test server.");
54+
}
55+
return {
56+
baseUrl: `http://127.0.0.1:${address.port}`,
57+
close: async () => {
58+
await new Promise((resolve, reject) => {
59+
server.close((error) => {
60+
if (error) reject(error);
61+
else resolve();
62+
});
63+
});
64+
}
65+
};
66+
}
67+
68+
test("workspace v2 launches asset manager and add/remove is reflected in export", async ({ page }) => {
69+
const server = await startRepoServer();
70+
try {
71+
await page.goto(`${server.baseUrl}/tools/workspace-v2/index.html`);
72+
73+
await page.getByRole("button", { name: "Full Reset" }).click();
74+
await page.locator("#workspaceV2ToolSelect").selectOption("asset-manager-v2");
75+
await page.getByRole("button", { name: "Load Fixture" }).click();
76+
await page.getByRole("button", { name: "Create Session + Launch" }).click();
77+
78+
await expect(page).toHaveURL(/\/tools\/asset-manager-v2\/index\.html/);
79+
await expect(page).toHaveTitle("Asset Manager V2");
80+
await expect(page.getByRole("button", { name: /Player Ship/ })).toBeVisible();
81+
82+
await page.locator("#assetManagerV2AddId").fill("asset-002");
83+
await page.locator("#assetManagerV2AddLabel").fill("Enemy Ship");
84+
await page.locator("#assetManagerV2AddKind").fill("svg");
85+
await page.locator("#assetManagerV2AddPath").fill("assets/vectors/enemy-ship.svg");
86+
await page.getByRole("button", { name: "Add Asset" }).click();
87+
88+
await expect(page.getByRole("button", { name: /Enemy Ship/ })).toBeVisible();
89+
await page.getByRole("button", { name: "Remove asset-002" }).click();
90+
await expect(page.getByRole("button", { name: /Enemy Ship/ })).toHaveCount(0);
91+
92+
await page.getByRole("button", { name: /Back to Workspace V2/ }).click();
93+
await expect(page).toHaveURL(/\/tools\/workspace-v2\/index\.html/);
94+
95+
const downloadPromise = page.waitForEvent("download");
96+
await page.getByRole("button", { name: "Export Workspace Session JSON" }).click();
97+
const download = await downloadPromise;
98+
const downloadPath = await download.path();
99+
if (!downloadPath) {
100+
throw new Error("Workspace export did not produce a downloadable file.");
101+
}
102+
const exportedJsonText = await fs.readFile(downloadPath, "utf8");
103+
const exported = JSON.parse(exportedJsonText);
104+
const entries = exported?.tools?.["workspace-v2"]?.activeSession?.payloadJson?.assetCatalog?.entries;
105+
if (!Array.isArray(entries)) {
106+
throw new Error("Exported manifest is missing tools.workspace-v2.activeSession.payloadJson.assetCatalog.entries.");
107+
}
108+
expect(entries.some((entry) => entry?.id === "asset-001")).toBe(true);
109+
expect(entries.some((entry) => entry?.id === "asset-002")).toBe(false);
110+
} finally {
111+
await server.close();
112+
}
113+
});

0 commit comments

Comments
 (0)