venetian 0.2.0-aarch64-linux → 0.2.2-aarch64-linux

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/exe/aarch64-linux/LICENSE +202 -202
  4. data/exe/aarch64-linux/node +0 -0
  5. data/exe/aarch64-linux/package/browsers.json +12 -18
  6. data/exe/aarch64-linux/package/lib/bootstrap.js +1 -1
  7. data/exe/aarch64-linux/package/lib/coreBundle.js +11344 -9900
  8. data/exe/aarch64-linux/package/lib/server/electron/loader.js +5 -2
  9. data/exe/aarch64-linux/package/lib/serverRegistry.js +5 -1
  10. data/exe/aarch64-linux/package/lib/tools/cli-client/help.json +21 -6
  11. data/exe/aarch64-linux/package/lib/tools/cli-client/program.js +1 -1
  12. data/exe/aarch64-linux/package/lib/tools/cli-client/session.js +4 -0
  13. data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/SKILL.md +21 -5
  14. data/exe/aarch64-linux/package/lib/tools/{cli-client/skill/references/spec-driven-testing.md → skills/playwright-cli/references/test-generation.md} +135 -7
  15. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  16. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  17. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  18. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  19. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  20. data/exe/aarch64-linux/package/lib/tools/utils/extension.js +31 -8
  21. data/exe/aarch64-linux/package/lib/utilsBundle.js +37698 -33670
  22. data/exe/aarch64-linux/package/lib/utilsBundle.js.LICENSE +391 -268
  23. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  24. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  25. data/exe/aarch64-linux/package/lib/vite/dashboard/index.html +2 -2
  26. data/exe/aarch64-linux/package/lib/vite/htmlReport/report.css +2 -1
  27. data/exe/aarch64-linux/package/lib/vite/htmlReport/report.js +15 -55
  28. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  29. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  30. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  31. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  32. data/exe/aarch64-linux/package/lib/vite/recorder/index.html +2 -2
  33. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/codeMirrorModule-rXmQmLUY.js +32 -0
  34. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/defaultSettingsView-B-dXF5JN.js +181 -0
  35. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  36. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/xtermModule-BuZfJS5v.js +7 -0
  37. data/exe/aarch64-linux/package/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  38. data/exe/aarch64-linux/package/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  39. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  40. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.KZ4wOW1K.js +1 -0
  41. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.html +5 -5
  42. data/exe/aarch64-linux/package/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  43. data/exe/aarch64-linux/package/lib/vite/traceViewer/snapshot.html +2 -2
  44. data/exe/aarch64-linux/package/lib/vite/traceViewer/sw.bundle.js +3 -4
  45. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  46. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.Dzuouizj.js +5 -0
  47. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.html +5 -5
  48. data/exe/aarch64-linux/package/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  49. data/exe/aarch64-linux/package/lib/webp_codec.LICENSE +173 -0
  50. data/exe/aarch64-linux/package/lib/webp_codec.wasm +0 -0
  51. data/exe/aarch64-linux/package/lib/xdg-open +338 -137
  52. data/exe/aarch64-linux/package/package.json +2 -2
  53. data/exe/aarch64-linux/package/types/protocol.d.ts +249 -36
  54. data/exe/aarch64-linux/package/types/structs.d.ts +8 -1
  55. data/exe/aarch64-linux/package/types/types.d.ts +2009 -266
  56. data/lib/venetian/executable.rb +6 -2
  57. data/lib/venetian/upstream.rb +35 -18
  58. data/lib/venetian/version.rb +2 -2
  59. metadata +41 -38
  60. data/exe/aarch64-linux/package/api.json +0 -1
  61. data/exe/aarch64-linux/package/lib/server/deviceDescriptorsSource.json +0 -2739
  62. data/exe/aarch64-linux/package/lib/tools/cli-client/skill/references/test-generation.md +0 -134
  63. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-BY2S1tHT.css +0 -1
  64. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-C_5TMfeg.js +0 -52
  65. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +0 -1
  66. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +0 -32
  67. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-4ZiSSCmn.css +0 -1
  68. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-Bq-mQf8S.js +0 -193
  69. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +0 -32
  70. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +0 -264
  71. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +0 -1
  72. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +0 -9
  73. data/exe/aarch64-linux/package/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +0 -1
  74. data/exe/aarch64-linux/package/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +0 -1
  75. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.CzXZzn5A.css +0 -1
  76. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.DMMX1gXU.js +0 -2
  77. data/exe/aarch64-linux/package/lib/vite/traceViewer/snapshot.v8KI4P3m.js +0 -2
  78. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +0 -1
  79. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +0 -6
  80. data/exe/aarch64-linux/package/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +0 -32
  81. data/exe/aarch64-linux/package/protocol.yml +0 -4991
  82. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/element-attributes.md +0 -0
  83. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/playwright-tests.md +0 -0
  84. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/request-mocking.md +0 -0
  85. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/running-code.md +0 -0
  86. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/session-management.md +0 -0
  87. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/storage-state.md +0 -0
  88. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/tracing.md +0 -0
  89. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/video-recording.md +0 -0
  90. /data/exe/aarch64-linux/package/lib/tools/{trace → skills/playwright-trace}/SKILL.md +0 -0
@@ -0,0 +1,144 @@
1
+ # Gallery contract
2
+
3
+ The **gallery** is a single page, served by your dev server at the URL you set as `baseURL` in your
4
+ Playwright config, that exposes two methods on `window` for Playwright to drive:
5
+
6
+ - `window.mount(params)` — render a story.
7
+ - `window.unmount()` — unmount the current story.
8
+
9
+ The built-in `mount` fixture navigates to the gallery, then calls `window.mount` via
10
+ `page.evaluate()`. Keep props to plain serializable data — where the component takes callbacks,
11
+ the story creates the state, provides the callbacks and records the state into a hidden form for
12
+ the test to assert on.
13
+
14
+ ## `window.mount(params)`
15
+
16
+ `params` is `{ story, props }`, straight from the test's `mount(story, props)` call:
17
+
18
+ - `story` — the story id (string). Resolve it (see id grammar) to a component.
19
+ - `props` — the plain serializable props object passed to the component.
20
+
21
+ Render the resolved component with `props` into `#root`. Return a `Promise` that resolves once the
22
+ component is mounted and **rejects on failure** (unknown story, render throw). The rejection
23
+ surfaces as the test's `await mount(...)` throwing, with a real stack — there is no HTTP-status or
24
+ DOM-attribute signalling.
25
+
26
+ **Reuse the root across calls.** The test's `component.update(props)` calls `window.mount` again
27
+ with the same story and new props, **without navigating**. If you render into the same root /
28
+ instance rather than recreating it, the framework reconciles and component-internal state is
29
+ preserved — that is CT's `update()`. Recreating the root each call (or navigating) resets state, so
30
+ reuse it: create it on first mount and render into it on every call. The framework reconciles,
31
+ remounting on its own only when the story (component type) changes.
32
+
33
+ **`window.mount` is your setup/teardown hook.** It is the browser-side equivalent of CT's
34
+ `beforeMount` / `afterMount`: install providers or plugins, seed a store, start an in-browser mock
35
+ server *before* you render, and run post-render work *after* — all inside this one function,
36
+ branched on the `story` / `props` the test passed. There is no separate hook registry; the function
37
+ you own is the hook.
38
+
39
+ ## `window.unmount()`
40
+
41
+ Unmount the current story from `#root` and return a `Promise`. The test calls it via
42
+ `component.unmount()`. Needed only to assert teardown/cleanup effects — each `mount` navigates
43
+ fresh, so tests are already isolated.
44
+
45
+ ## `#root`
46
+
47
+ Render the component into an element with `id="root"`. `mount` returns a `Locator` for `#root`
48
+ itself, so tests scope their queries from there — `component.getByRole('button').click()`, not
49
+ `component.click()`. Stories are free to render fragments, e.g. the component plus a hidden form
50
+ recording its state.
51
+
52
+ ## Story id grammar (recommended)
53
+
54
+ The gallery owns resolution; `mount` passes the id through untouched. Recommended scheme:
55
+
56
+ - `<path under src, without the .story.* extension>/<ExportName>` — e.g.
57
+ `src/components/Button.story.tsx` export `Primary` → `components/Button/Primary`.
58
+ - Any unique trailing suffix resolves too: `Button/Primary`.
59
+ - A single-file-component story (`Button.story.vue`) is one story, addressed by its path alone
60
+ (its default export): `components/Button`.
61
+
62
+ ## Worked example (React + Vite SPA)
63
+
64
+ An illustration of the contract, **not** a file to copy — implement the equivalent for your stack.
65
+ `import.meta.glob` stays inline here: Vite analyzes it statically, relative to this file, so it
66
+ cannot be moved into shared/shipped code. That is exactly why the gallery is yours to own.
67
+
68
+ ```tsx
69
+ // playwright/gallery/main.tsx
70
+ import { flushSync } from 'react-dom';
71
+ import { createRoot, type Root } from 'react-dom/client';
72
+
73
+ const stories = import.meta.glob('../../src/**/*.story.{tsx,jsx}');
74
+ const id = (f: string) => f.replace(/^(\.\.\/)+src\//, '').replace(/\.story\.\w+$/, '');
75
+
76
+ async function resolve(storyId: string) {
77
+ const sep = storyId.lastIndexOf('/');
78
+ const [path, name] = [storyId.slice(0, sep), storyId.slice(sep + 1)];
79
+ const file = Object.keys(stories).find(f => id(f) === path || id(f).endsWith('/' + path));
80
+ const mod = (file && await stories[file]()) as Record<string, any> | undefined;
81
+ return mod?.[name] ?? mod?.default;
82
+ }
83
+
84
+ const rootEl = document.getElementById('root')!;
85
+ let root: Root | undefined;
86
+
87
+ (window as any).mount = async ({ story, props }: { story: string, props?: Record<string, any> }) => {
88
+ const Story = await resolve(story);
89
+ if (!Story)
90
+ throw new Error(`Unknown story: ${story}`);
91
+ root ??= createRoot(rootEl); // reuse the root so update() reconciles and preserves state
92
+ // flushSync so a render error rejects the promise instead of being swallowed.
93
+ flushSync(() => root!.render(<Story {...props} />));
94
+ };
95
+
96
+ (window as any).unmount = async () => {
97
+ root?.unmount();
98
+ root = undefined;
99
+ };
100
+ ```
101
+
102
+ ```html
103
+ <!-- playwright/gallery/index.html -->
104
+ <!DOCTYPE html>
105
+ <div id="root"></div>
106
+ <script type="module" src="./main.tsx"></script>
107
+ ```
108
+
109
+ ## Vue variant (state-preserving)
110
+
111
+ Vue's `createApp(...).mount()` builds a fresh instance each call, so mount a small **reactive host**
112
+ once and update its refs — updating them re-renders in place, which is what preserves state across
113
+ `update()`:
114
+
115
+ ```ts
116
+ // playwright/gallery/main.ts
117
+ import { createApp, h, shallowRef, type App, type Component } from 'vue';
118
+
119
+ // resolve() and the import.meta.glob are the same as the React example.
120
+ const story = shallowRef<Component | null>(null);
121
+ const props = shallowRef<Record<string, any>>({});
122
+ const host = { render: () => (story.value ? h(story.value, props.value) : null) };
123
+ let app: App | undefined;
124
+
125
+ (window as any).mount = async ({ story: id, props: next }: { story: string, props?: Record<string, any> }) => {
126
+ const resolved = await resolve(id);
127
+ if (!resolved)
128
+ throw new Error(`Unknown story: ${id}`);
129
+ story.value = resolved;
130
+ props.value = next ?? {};
131
+ if (!app) { // mount once; the ref updates above re-render in place
132
+ app = createApp(host);
133
+ app.mount('#root');
134
+ }
135
+ };
136
+
137
+ (window as any).unmount = async () => {
138
+ app?.unmount();
139
+ app = undefined;
140
+ };
141
+ ```
142
+
143
+ Keep the story-resolution glob and the framework mount in this file; everything
144
+ else lives in your stories and tests.
@@ -0,0 +1,85 @@
1
+ # Migrating from @playwright/experimental-ct-react / -vue
2
+
3
+ The CT packages compiled JSX in the test file and marshalled it into the browser. The gallery
4
+ pattern moves the scenario into a story export that runs natively in the browser: structure (which
5
+ component, its children, providers) plus behavior (state and callbacks, recorded into a hidden
6
+ form for the test to assert on). Plain data props travel through `mount(storyId, props)`;
7
+ `update()` and `unmount()` work as before.
8
+
9
+ ## Concept mapping
10
+
11
+ | `@playwright/experimental-ct-*` | Gallery pattern |
12
+ |---|---|
13
+ | `mount(<Button title="…" onClick={spy} />)` | Stateful story: the story provides `onClick`, records the effect into a hidden form input; the test asserts `toHaveValue()` |
14
+ | Plain data props from the test | Unchanged in spirit: `mount(id, props)` |
15
+ | JSX children / slots from the test | Cannot cross — bake each composition into its own story export (Vue: `.story.vue` for slot-heavy scenarios) |
16
+ | `component.update(<Button count={2} />)` | `component.update({ count: 2 })` — state-preserving, needs the gallery to reuse its root (`gallery-spec.md`) |
17
+ | `component.unmount()` | `component.unmount()` — backed by the gallery's `window.unmount()` |
18
+ | `beforeMount`/`afterMount` in `playwright/index.ts` | The body of the gallery's `window.mount` (global), or story decorators (per-story) |
19
+ | `hooksConfig` per-test variation | Props: `mount('App/Routing', { route: '/dashboard' })` — the story/decorator interprets them |
20
+ | `router` fixture / MSW handlers in Node | `page.route()` in the test, or MSW `setupWorker` inside a story/decorator |
21
+ | `playwright/index.html` (styles, fonts, theme) | The gallery's `index.html` / entry module imports |
22
+ | `ctViteConfig`, `ctPort`, `ctTemplateDir`, `ctCacheDir` | Gone — the gallery runs through the app's own dev server; port lives in `webServer` + `baseURL`; location is `playwright/gallery/` |
23
+ | `defineConfig` from `@playwright/experimental-ct-react` | Plain `defineConfig` from `@playwright/test`, with `baseURL` = gallery URL, `serviceWorkers: 'block'`, `reuseContext: true` (see `SKILL.md`) |
24
+
25
+ ## Steps
26
+
27
+ 1. Set up the gallery and config per `SKILL.md`. Keep the old CT project running until the last
28
+ spec is migrated.
29
+ 2. For each CT spec, split every `mount(<…/>)` call: JSX structure becomes a story export next to
30
+ the component; plain data props stay in the test as `mount`'s second argument. Callback spies
31
+ become story state recorded into a hidden form. A call site that only varies data props usually
32
+ needs just one generic story that spreads them:
33
+ `export const Default = (props: ButtonProps) => <Button title="Submit" {...props} />`.
34
+ 3. Rewrite the spec: import `test`/`expect` from `@playwright/test`; `mount(<X a={1}/>)`
35
+ → `mount('X/Default', { a: 1 })`; `update(<X a={2}/>)` → `update({ a: 2 })`;
36
+ `unmount()` unchanged. `mount` returns a locator for the gallery root — scope the queries:
37
+ `component.getByRole('button').click()`. Spy assertions become `toHaveValue()` on the story's
38
+ recorded state.
39
+ 4. Port `beforeMount` hooks: app-wide setup into the gallery's `window.mount`; per-test
40
+ `hooksConfig` branches into props interpreted by a story or decorator.
41
+ 5. When all specs are green, delete the CT project from the config, drop the
42
+ `@playwright/experimental-ct-*` dependency, and remove `playwright/index.html`,
43
+ `playwright/index.ts*` and `playwright/.cache`.
44
+
45
+ ## Gotchas
46
+
47
+ - **Story ids are strings.** Renaming or moving a story breaks specs at runtime, not compile time
48
+ — and the suffix-matching resolution can silently match a different story after a rename.
49
+ - **Per-test JSX is gone.** Any test that built a different JSX tree per test (children matrices,
50
+ inline wrappers) becomes one story export per composition.
51
+
52
+ ## Before / after
53
+
54
+ ```tsx
55
+ // Before (CT)
56
+ import { test, expect } from '@playwright/experimental-ct-react';
57
+ import Button from '../src/components/Button';
58
+
59
+ test('click', async ({ mount }) => {
60
+ const messages: string[] = [];
61
+ const component = await mount(<Button title="Submit" onClick={data => messages.push(data)} />);
62
+ await component.click();
63
+ expect(messages).toEqual(['hello']);
64
+ });
65
+ ```
66
+
67
+ ```tsx
68
+ // After: src/components/Button.story.tsx
69
+ import Button from './Button';
70
+
71
+ export const Default = (props: { onClick?: (data: string) => void }) =>
72
+ <Button title="Submit" {...props} />;
73
+ ```
74
+
75
+ ```ts
76
+ // After: src/components/Button.spec.ts
77
+ import { test, expect } from '@playwright/test';
78
+
79
+ test('click', async ({ mount }) => {
80
+ const messages: string[] = [];
81
+ const component = await mount('components/Button/Default', { onClick: (data: string) => messages.push(data) });
82
+ await component.click();
83
+ expect(messages).toEqual(['hello']);
84
+ });
85
+ ```
@@ -0,0 +1,67 @@
1
+ # React setup
2
+
3
+ Follow the setup workflow in `SKILL.md`. Implement the gallery per `references/gallery-spec.md` (which has a React worked example); this page covers React-specific details.
4
+
5
+ ## Files
6
+
7
+ - `playwright/gallery/` — the gallery you implement to `references/gallery-spec.md` (an `index.html` + a `main.tsx` module). Requires `react` and `react-dom` 18+ (`createRoot`).
8
+ - Stories: `src/**/*.story.tsx` (the glob also picks up `.story.jsx`); example in `templates/react/Button.story.tsx`.
9
+
10
+ ## StrictMode
11
+
12
+ Wrap the rendered story in `<React.StrictMode>` in your gallery to match how most apps render. In development builds StrictMode intentionally double-invokes render functions and effects. This matters for stories that record events with counters set in effects — recording via state updates from event handlers (like the `CountsClicks` example story) is unaffected. If a story misbehaves under StrictMode, that is usually a real finding about the component; drop the wrapper only if the app itself does not use StrictMode.
13
+
14
+ ## Global providers
15
+
16
+ If components require context (theme, store, i18n, router), create one shared decorator and use it in stories, so each story states its scenario and nothing more:
17
+
18
+ ```tsx
19
+ // src/stories/decorators.tsx
20
+ import { ThemeProvider } from '../theme';
21
+ import { MemoryRouter } from 'react-router-dom';
22
+
23
+ export function AppScaffold({ children, route = '/' }: { children: React.ReactNode, route?: string }) {
24
+ return (
25
+ <ThemeProvider theme="light">
26
+ <MemoryRouter initialEntries={[route]}>{children}</MemoryRouter>
27
+ </ThemeProvider>
28
+ );
29
+ }
30
+ ```
31
+
32
+ ```tsx
33
+ // src/components/ProfilePage.story.tsx
34
+ export const LoggedIn = () => (
35
+ <AppScaffold route="/profile/42">
36
+ <ProfilePage user={{ id: 42, name: 'Test User' }} />
37
+ </AppScaffold>
38
+ );
39
+ ```
40
+
41
+ Do not build the decorator into the gallery — keeping it in story files makes the wrapping visible and lets stories opt out.
42
+
43
+ ## Typed props
44
+
45
+ `mount` is generic over the story: pass the story type as a template argument to type-check per-test props (and `update()`). Props are inferred from the component signature; function and class components both work.
46
+
47
+ ```tsx
48
+ // src/components/Button.story.tsx
49
+ export const WithTitle = ({ title = 'Default' }: { title?: string }) =>
50
+ <Button title={title} />;
51
+ ```
52
+
53
+ ```ts
54
+ // src/components/button.spec.ts
55
+ import type { WithTitle } from './Button.story';
56
+
57
+ const component = await mount<typeof WithTitle>('components/Button/WithTitle', { title: 'Hello' });
58
+ ```
59
+
60
+ ## CSS
61
+
62
+ - Global stylesheets: import them in your gallery entry (`playwright/gallery/main.tsx`, e.g. `import '../../src/index.css'`), mirroring the app's own entry point.
63
+ - Tailwind: if content scanning is path-based, make sure `*.story.tsx` files are covered.
64
+
65
+ ## Data fetching
66
+
67
+ For libraries with client objects (React Query, Apollo), create the client inside the story or decorator so each navigation starts fresh.
@@ -0,0 +1,75 @@
1
+ # Vue setup
2
+
3
+ Follow the setup workflow in `SKILL.md`. Implement the gallery per `references/gallery-spec.md`, exposing `window.mount`/`window.unmount` — in Vue, mount with `app = createApp(h(story, props)); app.mount('#root')` and unmount with `app.unmount()`; this page covers Vue-specific details.
4
+
5
+ ## Files
6
+
7
+ - `playwright/gallery/` — the gallery you implement to `references/gallery-spec.md` (an `index.html` + a `main.ts` module). Requires Vue 3.
8
+ - Stories: `src/**/*.story.{ts,js,vue}`; example in `templates/vue/Button.story.ts`.
9
+
10
+ ## Two ways to write stories
11
+
12
+ **Render-function stories** (`Button.story.ts`) — several scenarios per file, one named export each; see `templates/vue/Button.story.ts`. Uses `defineComponent` + `h()`, no SFC compilation involved.
13
+
14
+ **Single-file-component stories** (`Button.primary.story.vue`) — one story per file, full template syntax:
15
+
16
+ ```vue
17
+ <script setup lang="ts">
18
+ import { ref } from 'vue';
19
+ import Button from './Button.vue';
20
+ const clicks = ref(0);
21
+ </script>
22
+
23
+ <template>
24
+ <Button title="Submit" @click="clicks++" />
25
+ <form hidden><input data-testid="click-count" readonly :value="String(clicks)" /></form>
26
+ </template>
27
+ ```
28
+
29
+ An SFC story is addressed by its path without the extension: `mount('components/Button.primary')`. Prefer SFC stories when the scenario needs slots or non-trivial templates.
30
+
31
+ ## Global plugins
32
+
33
+ Apps that rely on plugins (Pinia, vue-router, i18n) should wrap components with a decorator story helper that creates a fresh instance per story:
34
+
35
+ ```ts
36
+ // src/stories/decorators.ts
37
+ import { defineComponent, h, type Component } from 'vue';
38
+ import { createPinia } from 'pinia';
39
+
40
+ export function withStore(story: Component) {
41
+ return defineComponent(() => {
42
+ const pinia = createPinia();
43
+ return () => h(story, { pinia });
44
+ });
45
+ }
46
+ ```
47
+
48
+ For plugins that must be installed on the app instance (`app.use(...)`), add them in your gallery right after `createApp(...)` — that is the equivalent of the app's own bootstrap.
49
+
50
+ ## Typed props
51
+
52
+ A story that takes per-test props declares them twice: in the setup signature (for the type) and in the `props` option (so Vue delivers them as props rather than `attrs`):
53
+
54
+ ```ts
55
+ // src/components/Button.story.ts
56
+ export const WithTitle = defineComponent(
57
+ (props: { title?: string }) => () => h(Button, { title: props.title ?? 'Default' }),
58
+ { props: ['title'] },
59
+ );
60
+ ```
61
+
62
+ `mount` is generic over the story: pass the story type as a template argument to type-check the props (and `update()`):
63
+
64
+ ```ts
65
+ // src/components/button.spec.ts
66
+ import type { WithTitle } from './Button.story';
67
+
68
+ const component = await mount<typeof WithTitle>('components/Button/WithTitle', { title: 'Hello' });
69
+ ```
70
+
71
+ Options-API stories (`defineComponent({ props: { ... } })`) infer props the same way. For `.story.vue` SFC stories, prop types are only inferable when the setup generates SFC types (Volar/vue-tsc); otherwise pass the props type directly: `mount<{ title?: string }>('components/Button.primary', { title: 'Hello' })`.
72
+
73
+ ## CSS
74
+
75
+ Import global stylesheets in your gallery entry (`playwright/gallery/main.ts`, e.g. `import '../../src/assets/main.css'`), mirroring the app's entry point.
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
  var extension_exports = {};
30
30
  __export(extension_exports, {
31
+ findPlaywrightExtensionProfile: () => findPlaywrightExtensionProfile,
31
32
  isPlaywrightExtensionInstalled: () => isPlaywrightExtensionInstalled,
32
33
  playwrightExtensionId: () => playwrightExtensionId,
33
34
  playwrightExtensionInstallUrl: () => playwrightExtensionInstallUrl
@@ -37,20 +38,41 @@ var import_fs = __toESM(require("fs"));
37
38
  var import_path = __toESM(require("path"));
38
39
  const playwrightExtensionId = "mmlmfjhmonkocbjadbfplnigmagldckm";
39
40
  const playwrightExtensionInstallUrl = `https://chromewebstore.google.com/detail/playwright-extension/${playwrightExtensionId}`;
40
- async function isPlaywrightExtensionInstalled(userDataDir) {
41
+ async function findPlaywrightExtensionProfile(userDataDir) {
42
+ const profiles = await listProfileDirectories(userDataDir);
43
+ const lastUsed = await readLastUsedProfile(userDataDir);
44
+ const ordered = lastUsed && profiles.includes(lastUsed) ? [lastUsed, ...profiles.filter((profile) => profile !== lastUsed)] : profiles;
45
+ for (const profile of ordered) {
46
+ if (await isExtensionInstalledInProfile(import_path.default.join(userDataDir, profile)))
47
+ return profile;
48
+ }
49
+ return void 0;
50
+ }
51
+ async function listProfileDirectories(userDataDir) {
41
52
  let entries;
42
53
  try {
43
54
  entries = await import_fs.default.promises.readdir(userDataDir);
44
55
  } catch {
45
- return false;
56
+ return [];
46
57
  }
47
- for (const entry of entries) {
48
- if (entry !== "Default" && !entry.startsWith("Profile "))
49
- continue;
50
- if (await isExtensionInstalledInProfile(import_path.default.join(userDataDir, entry)))
51
- return true;
58
+ const profiles = entries.filter((entry) => entry === "Default" || /^Profile \d+$/.test(entry));
59
+ profiles.sort((a, b) => profileRank(a) - profileRank(b));
60
+ return profiles;
61
+ }
62
+ function profileRank(profile) {
63
+ return profile === "Default" ? -1 : parseInt(profile.slice("Profile ".length), 10);
64
+ }
65
+ async function readLastUsedProfile(userDataDir) {
66
+ try {
67
+ const localState = JSON.parse(await import_fs.default.promises.readFile(import_path.default.join(userDataDir, "Local State"), "utf-8"));
68
+ const lastUsed = localState?.profile?.last_used;
69
+ return typeof lastUsed === "string" ? lastUsed : void 0;
70
+ } catch {
71
+ return void 0;
52
72
  }
53
- return false;
73
+ }
74
+ async function isPlaywrightExtensionInstalled(userDataDir) {
75
+ return await findPlaywrightExtensionProfile(userDataDir) !== void 0;
54
76
  }
55
77
  async function isExtensionInstalledInProfile(profileDir) {
56
78
  if (await pathExists(import_path.default.join(profileDir, "Extensions", playwrightExtensionId)))
@@ -72,6 +94,7 @@ async function pathExists(p) {
72
94
  }
73
95
  // Annotate the CommonJS export names for ESM import in node:
74
96
  0 && (module.exports = {
97
+ findPlaywrightExtensionProfile,
75
98
  isPlaywrightExtensionInstalled,
76
99
  playwrightExtensionId,
77
100
  playwrightExtensionInstallUrl