document360-engine 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -2,43 +2,69 @@
|
|
|
2
2
|
|
|
3
3
|
**Activate when** you've just emitted a `<!-- SCREENSHOT ... -->` placeholder block in an article, OR the user runs `/screenshot <id>`.
|
|
4
4
|
|
|
5
|
-
**Goal:** generate a Playwright `.spec.ts`
|
|
5
|
+
**Goal:** generate a Playwright `.spec.ts` in the configured `captureDir` that the `document360-capture` (alias `d360-capture`) CLI runs against the user's live product.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Read the product FIRST — never guess (non-negotiable)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Every recurring capture failure has traced back to guessing. Before you write a single line, READ the source and ground each of these in it:
|
|
10
|
+
|
|
11
|
+
1. **Routes** — find the router (e.g. the app's route table) and use the EXACT path. Do not assume `/spec` vs `/spec-files`, `/tests` vs `/test`. If you can't find it, say so and ask.
|
|
12
|
+
2. **Selectors** — open the component and use a stable `data-testid` you actually see. Never invent one, and never fall back to `li`, `ul.divide-y li`, or other structural CSS.
|
|
13
|
+
3. **Context selection** — find how the app enters the context the shot needs (selecting a project / workspace / org / etc.). You'll drive it with the capture *scope* (below), not by clicking "the first one".
|
|
14
|
+
4. **Data prerequisites** — determine what state the screen needs to look real (≥1 of something, a specific record, an open modal). This becomes both the placeholder's `prerequisites:` and a runtime guard (below).
|
|
15
|
+
|
|
16
|
+
If a fact isn't in the source, do not fabricate it.
|
|
17
|
+
|
|
18
|
+
## Capture scope — enter a KNOWN context, deterministically
|
|
19
|
+
|
|
20
|
+
The capture profile declares a generic `scope` (key→value) for the prepared demo context — e.g. `{project: "Docs Demo"}`, or `{project, workspace, language}`, or `{org}` — whatever THIS product scopes by (you learned that by reading the source; it is not always "project"). The CLI injects it; read it with the `captureScope()` / `scopeValue(key)` helpers.
|
|
21
|
+
|
|
22
|
+
- Select the context using the scope value (e.g. the project whose name === `scope.project`), via the selection mechanism you read from the source — **not** `.first()`.
|
|
23
|
+
- Fall back to `.first()` ONLY when the relevant scope key is unset, so single-context setups still work.
|
|
24
|
+
|
|
25
|
+
## Data prerequisites — skip clearly, don't time out
|
|
26
|
+
|
|
27
|
+
If the data the shot needs is absent, a 15s selector timeout is a terrible error. Instead, guard it and skip with a reason that tells the user exactly what to stage:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
if ((await rows.count()) === 0) {
|
|
31
|
+
test.skip(true, 'Prerequisite not met: needs ≥1 suite in project "' + (scopeValue('project') ?? '<scope>') + '". Stage it, then re-run.');
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The skip reason MUST match what you wrote in the placeholder's `prerequisites:` line — they're the same fact, surfaced two ways.
|
|
10
36
|
|
|
11
37
|
## Spec template
|
|
12
38
|
|
|
13
39
|
```typescript
|
|
14
40
|
import { test } from '@playwright/test';
|
|
15
|
-
import { waitPastLogin, dumpAnnotations, type Placeholder } from 'document360-capture/helpers';
|
|
41
|
+
import { waitPastLogin, dumpAnnotations, captureScope, scopeValue, type Placeholder } from 'document360-capture/helpers';
|
|
16
42
|
|
|
17
43
|
const PLACEHOLDER: Placeholder = {
|
|
18
44
|
id: '<placeholder.id>',
|
|
19
45
|
saveTo: '<placeholder.save-to>',
|
|
20
|
-
highlight: [
|
|
21
|
-
|
|
22
|
-
],
|
|
23
|
-
annotations: [
|
|
24
|
-
// { target: '<stable selector>', label: '1', text: '<short text>' }
|
|
25
|
-
],
|
|
26
|
-
redact: [
|
|
27
|
-
// one stable selector per placeholder.redact entry
|
|
28
|
-
],
|
|
46
|
+
highlight: [ /* one stable selector per placeholder.highlight entry */ ],
|
|
47
|
+
annotations: [ /* { target: '<stable selector>', label: '1', text: '<short text>' } */ ],
|
|
48
|
+
redact: [ /* one stable selector per placeholder.redact entry */ ],
|
|
29
49
|
};
|
|
30
50
|
|
|
31
51
|
test('<placeholder.id>', async ({ page }) => {
|
|
32
52
|
await page.goto(process.env.CAPTURE_START_URL!);
|
|
33
53
|
await waitPastLogin(page, new RegExp(process.env.CAPTURE_AUTH_BOUNDARY!));
|
|
34
54
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
//
|
|
55
|
+
// 1. Enter the prepared context using the scope (selection mechanism read from source).
|
|
56
|
+
const scope = captureScope();
|
|
57
|
+
// …select the context by scope.<key>; fall back to first only when that key is unset…
|
|
58
|
+
|
|
59
|
+
// 2. Navigate using the EXACT route read from the router (never a guessed path).
|
|
60
|
+
// …
|
|
61
|
+
|
|
62
|
+
// 3. Prerequisite guard — skip with a clear reason instead of timing out.
|
|
63
|
+
// if ((await <rows>.count()) === 0) test.skip(true, 'Prerequisite not met: …');
|
|
38
64
|
|
|
65
|
+
// 4. Reach the target state, settle, capture.
|
|
39
66
|
await page.waitForSelector('<target state selector>', { state: 'visible' });
|
|
40
67
|
await page.waitForTimeout(500);
|
|
41
|
-
|
|
42
68
|
await page.locator('<capture target selector>').screenshot({ path: PLACEHOLDER.saveTo });
|
|
43
69
|
await dumpAnnotations(page, PLACEHOLDER);
|
|
44
70
|
});
|
|
@@ -46,31 +72,27 @@ test('<placeholder.id>', async ({ page }) => {
|
|
|
46
72
|
|
|
47
73
|
## Selector quality rule (non-negotiable)
|
|
48
74
|
|
|
49
|
-
|
|
50
|
-
|
|
75
|
+
Stable selectors in priority order:
|
|
51
76
|
1. `[data-testid="..."]` — best.
|
|
52
77
|
2. `[aria-label="..."]` — good.
|
|
53
|
-
3.
|
|
54
|
-
4. Visible text (`text
|
|
78
|
+
3. `role=button[name="..."]` / `<role>:has-text("...")` — acceptable when unique.
|
|
79
|
+
4. Visible text (`text=...`) — acceptable for top-level nav and unique buttons.
|
|
55
80
|
|
|
56
|
-
**Never**
|
|
81
|
+
**Never** CSS classes, `nth-child`, structural `li`/`div.x`, deep DOM paths, or auto-generated IDs (`#mui-12345`).
|
|
57
82
|
|
|
58
|
-
## TODO escape hatch (when stable
|
|
59
|
-
|
|
60
|
-
If a required interaction has no stable selector, do NOT write a fragile one. Instead:
|
|
83
|
+
## TODO escape hatch (when a stable selector truly doesn't exist)
|
|
61
84
|
|
|
85
|
+
Don't write a fragile selector. Instead:
|
|
62
86
|
1. Use `test.skip(...)` instead of `test(...)`.
|
|
63
|
-
2.
|
|
64
|
-
3. Tell the user which file + component to fix
|
|
65
|
-
|
|
66
|
-
The manual intern path in the placeholder still works — only the automated capture is blocked.
|
|
87
|
+
2. Top-of-file comment: `// TODO: add data-testid="<name>" to <Component> at <src/.../File.tsx>. Capture disabled until then.`
|
|
88
|
+
3. Tell the user which file + component to fix and the `data-testid` to add.
|
|
67
89
|
|
|
68
90
|
## Capture region (the placeholder's `capture` field)
|
|
69
91
|
|
|
70
92
|
| `capture` value | Spec uses |
|
|
71
93
|
|---|---|
|
|
72
94
|
| `full-page` | `await page.screenshot({ path, fullPage: true })` |
|
|
73
|
-
| `main-panel` | a stable container selector (read it from the
|
|
95
|
+
| `main-panel` | a stable container selector (read it from the layout source) |
|
|
74
96
|
| `modal-only` | `page.locator('[role="dialog"]').first().screenshot(...)` |
|
|
75
97
|
| `panel-left` / `panel-right` | the project's named-panel selector |
|
|
76
98
|
| `sidebar` | the project's sidebar selector |
|
|
@@ -78,8 +100,9 @@ The manual intern path in the placeholder still works — only the automated cap
|
|
|
78
100
|
## After writing the spec
|
|
79
101
|
|
|
80
102
|
- Report the spec's path.
|
|
81
|
-
-
|
|
82
|
-
- Remind
|
|
103
|
+
- List anything that needs the user: the `data-testid`s a dev must add (TODO hatch), and the **data prerequisites** to stage (project/workspace + the records each shot needs).
|
|
104
|
+
- Remind how to capture (screenshots are a separate tool — not bundled with the writer):
|
|
83
105
|
1. First time only: `npm i -g document360-capture`
|
|
84
|
-
2.
|
|
85
|
-
3. `d360-capture
|
|
106
|
+
2. Set the capture `scope` for the prepared demo context in `.d360-capture.json`.
|
|
107
|
+
3. Once per machine: `d360-capture auth --profile <name>`
|
|
108
|
+
4. `d360-capture capture <id>`.
|