@quickpickle/vitest-browser 0.0.3 → 0.2.0

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 (57) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +71 -0
  3. package/dist/VitestBrowserWorld.cjs +89 -22
  4. package/dist/VitestBrowserWorld.cjs.map +1 -1
  5. package/dist/VitestBrowserWorld.d.ts +45 -21
  6. package/dist/VitestBrowserWorld.mjs +90 -23
  7. package/dist/VitestBrowserWorld.mjs.map +1 -1
  8. package/dist/actions.steps.cjs +26 -2
  9. package/dist/actions.steps.cjs.map +1 -1
  10. package/dist/actions.steps.mjs +27 -3
  11. package/dist/actions.steps.mjs.map +1 -1
  12. package/dist/frameworks/ReactBrowserWorld.cjs +19 -6
  13. package/dist/frameworks/ReactBrowserWorld.cjs.map +1 -1
  14. package/dist/frameworks/ReactBrowserWorld.d.ts +9 -0
  15. package/dist/frameworks/ReactBrowserWorld.mjs +19 -7
  16. package/dist/frameworks/ReactBrowserWorld.mjs.map +1 -1
  17. package/dist/frameworks/SvelteBrowserWorld.cjs +13 -2
  18. package/dist/frameworks/SvelteBrowserWorld.cjs.map +1 -1
  19. package/dist/frameworks/SvelteBrowserWorld.d.ts +2 -3
  20. package/dist/frameworks/SvelteBrowserWorld.mjs +13 -2
  21. package/dist/frameworks/SvelteBrowserWorld.mjs.map +1 -1
  22. package/dist/frameworks/VueBrowserWorld.cjs +8 -4
  23. package/dist/frameworks/VueBrowserWorld.cjs.map +1 -1
  24. package/dist/frameworks/VueBrowserWorld.d.ts +1 -3
  25. package/dist/frameworks/VueBrowserWorld.mjs +8 -4
  26. package/dist/frameworks/VueBrowserWorld.mjs.map +1 -1
  27. package/dist/frameworks/react.cjs +2 -0
  28. package/dist/frameworks/react.cjs.map +1 -1
  29. package/dist/frameworks/react.mjs +2 -0
  30. package/dist/frameworks/react.mjs.map +1 -1
  31. package/dist/frameworks/svelte.cjs +2 -0
  32. package/dist/frameworks/svelte.cjs.map +1 -1
  33. package/dist/frameworks/svelte.mjs +2 -0
  34. package/dist/frameworks/svelte.mjs.map +1 -1
  35. package/dist/frameworks/vue.cjs +2 -0
  36. package/dist/frameworks/vue.cjs.map +1 -1
  37. package/dist/frameworks/vue.mjs +2 -0
  38. package/dist/frameworks/vue.mjs.map +1 -1
  39. package/dist/outcomes.steps.cjs +78 -22
  40. package/dist/outcomes.steps.cjs.map +1 -1
  41. package/dist/outcomes.steps.mjs +78 -22
  42. package/dist/outcomes.steps.mjs.map +1 -1
  43. package/package.json +5 -4
  44. package/rollup.config.js +1 -0
  45. package/src/VitestBrowserWorld.ts +127 -38
  46. package/src/actions.steps.ts +28 -2
  47. package/src/frameworks/ReactBrowserWorld.ts +22 -9
  48. package/src/frameworks/SvelteBrowserWorld.ts +11 -3
  49. package/src/frameworks/VueBrowserWorld.ts +6 -5
  50. package/src/outcomes.steps.ts +79 -22
  51. package/tests/generic/browser-actions.feature +193 -0
  52. package/tests/generic/browser-generic.feature +76 -0
  53. package/tests/generic/browser-outcomes.feature +138 -0
  54. package/tests/generic/generic.steps.ts +27 -0
  55. package/tests/svelte/Example.svelte +153 -0
  56. package/vite.config.ts +8 -0
  57. package/vitest.workspace.ts +34 -35
package/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # @quickpickle/vitest-browser
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8273918: Switched to VisualWorld class for visual regression testing
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [e617638]
12
+ - quickpickle@1.9.0
13
+
14
+ ## 0.1.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 8a32a09: Add a true path sanitizer to the base World object
19
+
20
+ BREAKING CHANGE:
21
+
22
+ The base world object no longer has a public `projectRoot` property.
23
+ Instead of constructing paths that way, developers should use the
24
+ `world.fullPath()` method to get the full path to a file or subfolder.
25
+ This should ensure that only files below the projectRoot are accessed.
26
+
27
+ Playwright and Browser world constructors no longer have the
28
+ sanitizeFilepath method. Instead, there is a new "sanitizePath"
29
+ method on the base World, which should be relatively safe even
30
+ for user input, avoiding path traversal and the like.
31
+
32
+ - 15e0e06: Major refactor with breaking changes, including to the World Constructor APIs.
33
+
34
+ - Added tests for all step definitions (using Svelte) based on Playwright package tests
35
+ - Added basic instructions to README
36
+ - Added scrolling step definitions
37
+ - Added screenshot step definitions
38
+ - Added step definitions to test for integer values
39
+ - Added step definitions to check that an element is in the viewport
40
+ - Added step definitions to check for clicked items
41
+ - Changed the VitestBrowserWorld API to accommodate step definitions
42
+ - Changed the React, Vue and Svelte worlds to use the new API
43
+
44
+ BREAKING CHANGES:
45
+
46
+ - When extending the VitestBrowserWorld class, subclasses must implement the `render`
47
+ and `cleanup` methods. The `render` method must not only render the component
48
+ but also set the `page` property using RenderResult.container.
49
+
50
+ ### Patch Changes
51
+
52
+ - Updated dependencies [8a32a09]
53
+ - quickpickle@1.8.0
54
+
3
55
  ## 0.0.3
4
56
 
5
57
  ### Patch Changes
package/README.md CHANGED
@@ -1,4 +1,75 @@
1
1
  # Vitest: Browser Mode
2
2
 
3
3
  This is the library for using Vitest with browser mode for testing components.
4
+ It's still fairly early in development, but you can already use it for some purposes.
4
5
 
6
+ ## Setup
7
+
8
+ The following is a pretty basic setup for testing components in Svelte, Vue, or React:
9
+
10
+ 1. `pnpm i -D @quickpickle/vitest-browser@latest`
11
+
12
+ 2. add plugins to the Vitest configuration, in one of the configuration files (`vite.config.ts`, `vitest.config.ts`, or `vitest.workspace.ts`). Here is a working example from a SvelteKit project:
13
+
14
+ ```ts
15
+ // File: vitest.workspace.ts
16
+ import quickpickle from "quickpickle";
17
+
18
+ export default [
19
+ {
20
+ plugins: [quickpickle()],
21
+ extends: './vite.config.ts',
22
+ test: {
23
+ name: 'components',
24
+ environment: 'browser',
25
+ include: ['src/lib/**/*.feature'], // anticipates putting the .feature files next to components
26
+ setupFiles: ['./tests/components.steps.ts'], // this file must be created (see step 3)
27
+ // @ts-ignore
28
+ quickpickle: {
29
+ worldConfig: {
30
+ componentDir: 'src/lib', // The directory where the components are kept
31
+ }
32
+ },
33
+ browser: { // This is configuration for Vitest browser mode, and can be modified as appropriate
34
+ enabled: true,
35
+ screenshotFailures: true,
36
+ name: 'chromium',
37
+ provider: 'playwright',
38
+ ui: true,
39
+ instances: [
40
+ { browser:'chromium' },
41
+ ]
42
+ }
43
+ }
44
+ },
45
+ ]
46
+ ```
47
+
48
+ 3. Create a step definition file for your component tests:
49
+
50
+ ```ts
51
+ // File: tests/components.steps.ts
52
+ import '@quickpickle/vitest-browser/actions';
53
+ import '@quickpickle/vitest-browser/outcomes';
54
+ import '@quickpickle/vitest-browser/svelte'; // OR react or vue
55
+ ```
56
+
57
+ ## Known Issues:
58
+
59
+ * Reactivity is currently broken for Svelte and Vue tests.
60
+ * Selecting elements by css selector doesn't work yet.
61
+ * Performing screenshot comparisons may result in an extra screenshot
62
+ being created for @vitest/browser versions below 3.2.0
63
+
64
+ ## Suspected Issues:
65
+
66
+ * I suspect that there will still be issues with the test code
67
+ not properly waiting for changes to propagate on the page when
68
+ there are delays for CSS transitions, async fetch calls, etc.
69
+
70
+ ## Plans:
71
+
72
+ [x] basic actions and outcomes in English, to match @quickpickle/playwright
73
+ [x] basic tests for rendering Svelte, Vue, and React components
74
+ [x] full tests for all actions and outcomes, matching @quickpickle/playwright
75
+ [ ] some sort of Storybook-esque presentation using Vitest UI
@@ -2,45 +2,54 @@
2
2
 
3
3
  var quickpickle = require('quickpickle');
4
4
  var defaultsDeep = require('lodash/defaultsDeep');
5
+ var context = require('@vitest/browser/context');
6
+ var buffer = require('buffer');
5
7
 
6
8
  const defaultVitestWorldConfig = {
7
9
  componentDir: '', // directory in which components are kept, relative to project root
8
10
  screenshotDir: 'screenshots', // directory in which to save screenshots, relative to project root (default: "screenshots")
9
- screenshotOptions: {}, // options for the default screenshot comparisons
11
+ screenshotOpts: {
12
+ threshold: 0.1,
13
+ alpha: 0.6,
14
+ maxDiffPercentage: .01,
15
+ },
10
16
  };
11
- class VitestBrowserWorld extends quickpickle.QuickPickleWorld {
17
+ class VitestBrowserWorld extends quickpickle.VisualWorld {
18
+ async render(name, props, renderOptions) { }
19
+ ;
20
+ async cleanup() { }
21
+ ;
12
22
  constructor(context, info) {
13
23
  info = defaultsDeep(info || {}, { config: { worldConfig: defaultVitestWorldConfig } });
14
24
  super(context, info);
25
+ this.actions = {
26
+ clicks: [],
27
+ doubleClicks: [],
28
+ };
15
29
  if (!info.config.worldConfig.screenshotDir && info.config.worldConfig?.screenshotOptions?.customSnapshotsDir) {
16
30
  this.info.config.worldConfig.screenshotDir = info.config.worldConfig.screenshotOptions.customSnapshotsDir;
17
31
  }
18
- this.renderFn = () => { };
19
- this.cleanupFn = () => { };
20
32
  }
21
33
  async init() {
22
34
  let browserContext = await import('@vitest/browser/context');
23
- this.page = browserContext.page;
35
+ this.browserPage = browserContext.page;
24
36
  this.userEvent = browserContext.userEvent;
25
37
  }
26
- async render(name, props, renderOptions) {
27
- let component = typeof name === 'string'
28
- ? await import(`${this.projectRoot}/${this.worldConfig.componentDir}/${name}`.replace(/\/+/g, '/') /* @vite-ignore */)
29
- : name;
30
- await this.renderFn(component, props, renderOptions);
31
- }
32
- ;
33
- async cleanup() {
34
- await this.cleanupFn();
35
- }
36
- sanitizeFilepath(filepath) {
37
- return filepath.replace(/\/\/+/g, '/').replace(/\/[\.~]+\//g, '/');
38
- }
39
- get screenshotDir() {
40
- return this.sanitizeFilepath(`${this.projectRoot}/${this.worldConfig.screenshotDir}`);
38
+ get page() {
39
+ if (!this._page)
40
+ throw new Error('You must render a component before running tests.');
41
+ return this._page;
41
42
  }
42
- get screenshotFilename() {
43
- return `${this.toString().replace(/^.+?Feature: /, '').replace(' ' + this.info.step, '')}.png`;
43
+ set page(value) {
44
+ while (value.parentNode !== null && value.nodeName !== 'BODY')
45
+ value = value.parentNode;
46
+ this._page = this.browserPage.elementLocator(value);
47
+ value.addEventListener('click', (e) => {
48
+ this.actions.clicks.push(e.target);
49
+ });
50
+ value.addEventListener('dblclick', (e) => {
51
+ this.actions.doubleClicks.push(e.target);
52
+ });
44
53
  }
45
54
  /**
46
55
  * Gets a locator based on a certain logic
@@ -111,6 +120,8 @@ class VitestBrowserWorld extends quickpickle.QuickPickleWorld {
111
120
  async scroll(locator, direction, px = 100) {
112
121
  let horiz = direction.includes('t');
113
122
  let el = await locator.element();
123
+ if (el.nodeName === 'BODY' && el.parentElement)
124
+ el = el.parentElement;
114
125
  if (horiz)
115
126
  await el.scrollBy(direction === 'right' ? px : -px, 0);
116
127
  else
@@ -159,9 +170,65 @@ class VitestBrowserWorld extends quickpickle.QuickPickleWorld {
159
170
  if (toBePresent === (matchingElements.length === 0))
160
171
  throw new Error(`The${toBeVisible ? '' : ' hidden'} element "${locator}" was unexpectedly ${toBePresent ? 'not present' : 'present'}.`);
161
172
  }
173
+ async screenshot(opts) {
174
+ let path;
175
+ if (!opts?.bufferOnly)
176
+ path = this.getScreenshotPath(opts?.name);
177
+ let locator = opts?.locator ?? this.page;
178
+ return locator.screenshot({ path });
179
+ }
180
+ async expectScreenshotMatch(locator, filename, opts = {}) {
181
+ const filepath = this.getScreenshotPath(filename);
182
+ let expectedImg;
183
+ /**
184
+ * Load existing screenshot, or save it if it doesn't yet exist
185
+ */
186
+ try {
187
+ expectedImg = await context.commands.readFile(this.getScreenshotPath(filename), 'base64');
188
+ }
189
+ catch (e) {
190
+ // If the screenshot doesn't exist, save it and pass the test
191
+ await locator.screenshot();
192
+ console.warn(`new visual regression test: ${this.screenshotDir}/${this.screenshotFilename}`);
193
+ return;
194
+ }
195
+ let expected = buffer.Buffer.from(expectedImg, 'base64');
196
+ /**
197
+ * Get the screenshot
198
+ */
199
+ // type does not include the "save" option in the docs: see https://vitest.dev/guide/browser/locators#screenshot
200
+ let screenshotOptions = { save: false, base64: true };
201
+ let actualImg = await locator.screenshot(screenshotOptions);
202
+ let actual = buffer.Buffer.from(typeof actualImg === 'string' ? actualImg : actualImg.base64, 'base64');
203
+ /**
204
+ * Compare the two screenshots
205
+ */
206
+ let screenshotOpts = defaultsDeep(opts, this.worldConfig.screenshotOpts);
207
+ let matchResult = null;
208
+ try {
209
+ matchResult = await this.screenshotDiff(actual, expected, screenshotOpts);
210
+ }
211
+ catch (e) { }
212
+ console.log({
213
+ pass: matchResult?.pass,
214
+ diffPercentage: matchResult?.diffPercentage,
215
+ filename,
216
+ locator,
217
+ }.toString());
218
+ if (!matchResult?.pass) {
219
+ await context.commands.writeFile(`${filepath}.actual.png`, actual.toString('base64'), 'base64');
220
+ if (matchResult?.diff)
221
+ await context.commands.writeFile(`${filepath}.diff.png`, matchResult.diff.toString('base64'), 'base64');
222
+ throw new Error(`Screenshot does not match the snapshot.
223
+ Diff percentage: ${matchResult?.diffPercentage?.toFixed(2) ?? '100'}%
224
+ Max allowed: ${screenshotOpts.maxDiffPercentage}%
225
+ Diffs at: ${filepath}.(diff|actual).png`);
226
+ }
227
+ }
162
228
  /**
163
229
  * Waits for a certain amount of time
164
230
  * @param ms number
231
+ * @deprecated use `wait` method instead
165
232
  */
166
233
  async waitForTimeout(ms) {
167
234
  await new Promise(r => setTimeout(r, ms));
@@ -1 +1 @@
1
- {"version":3,"file":"VitestBrowserWorld.cjs","sources":["../src/VitestBrowserWorld.ts"],"sourcesContent":[null],"names":["QuickPickleWorld","Before"],"mappings":"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"}
1
+ {"version":3,"file":"VitestBrowserWorld.cjs","sources":["../src/VitestBrowserWorld.ts"],"sourcesContent":[null],"names":["VisualWorld","commands","Buffer","Before"],"mappings":"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"}
@@ -1,33 +1,50 @@
1
- import { QuickPickleWorld, QuickPickleWorldInterface } from 'quickpickle';
2
- import type { BrowserPage, Locator, UserEvent, ScreenshotOptions } from '@vitest/browser/context';
1
+ import type { BrowserPage, Locator, UserEvent } from '@vitest/browser/context';
3
2
  import type { TestContext } from 'vitest';
4
- import { InfoConstructor } from 'quickpickle/dist/world';
5
- export type VitestWorldConfig = {
3
+ import { ScreenshotComparisonOptions, VisualConfigSetting, VisualWorld, VisualWorldInterface, type InfoConstructor } from 'quickpickle';
4
+ export interface VitestWorldConfigSetting extends VisualConfigSetting {
6
5
  componentDir?: string;
7
- screenshotDir?: string;
8
- screenshotOptions?: Partial<ScreenshotOptions>;
6
+ }
7
+ export declare const defaultVitestWorldConfig: VitestWorldConfigSetting;
8
+ export type ActionsInterface = {
9
+ clicks: any[];
10
+ doubleClicks: any[];
9
11
  };
10
- export declare const defaultVitestWorldConfig: VitestWorldConfig;
11
- export type VitestBrowserWorldInterface = QuickPickleWorldInterface & {
12
+ export interface VitestBrowserWorldInterface extends VisualWorldInterface {
13
+ /**
14
+ * The `render` function must be provided by the World Constructor
15
+ * and must be tailored for the framework being used. It should render
16
+ * the component, and then use the parent element to set the `page` property
17
+ * of the World.
18
+ *
19
+ * @param name string|any The compoenent to render
20
+ * @param props any The properties to use when rendering the component
21
+ * @param renderOptions any Options to pass to the render function
22
+ * @returns Promise<void>
23
+ */
12
24
  render: (name: string | any, props?: any, renderOptions?: any) => Promise<void>;
13
- renderFn: (component: any, props?: any, renderOptions?: any) => void | Promise<void>;
25
+ /**
26
+ * The `cleanup` function must be provided by the World Constructor
27
+ * and must be tailored for the framework being used.
28
+ *
29
+ * @returns void
30
+ */
14
31
  cleanup: () => Promise<void>;
15
- cleanupFn: () => void | Promise<void>;
16
- page: BrowserPage;
32
+ actions: ActionsInterface;
33
+ browserPage: BrowserPage;
34
+ page: Locator;
17
35
  userEvent: UserEvent;
18
- };
19
- export declare class VitestBrowserWorld extends QuickPickleWorld implements VitestBrowserWorldInterface {
20
- renderFn: (component: any, props: any, renderOptions: any) => void;
21
- cleanupFn: () => void;
22
- page: BrowserPage;
36
+ }
37
+ export declare class VitestBrowserWorld extends VisualWorld implements VitestBrowserWorldInterface {
38
+ actions: ActionsInterface;
39
+ browserPage: BrowserPage;
23
40
  userEvent: UserEvent;
24
- constructor(context: TestContext, info: InfoConstructor);
25
- init(): Promise<void>;
26
41
  render(name: string | any, props?: any, renderOptions?: any): Promise<void>;
27
42
  cleanup(): Promise<void>;
28
- sanitizeFilepath(filepath: string): string;
29
- get screenshotDir(): string;
30
- get screenshotFilename(): string;
43
+ _page: Locator | null;
44
+ constructor(context: TestContext, info: InfoConstructor);
45
+ init(): Promise<void>;
46
+ get page(): Locator;
47
+ set page(value: HTMLElement);
31
48
  /**
32
49
  * Gets a locator based on a certain logic
33
50
  * @example getLocator(page, 'Cancel', 'button') => page.getByRole('button', { name: 'Cancel' })
@@ -95,9 +112,16 @@ export declare class VitestBrowserWorld extends QuickPickleWorld implements Vite
95
112
  * @param toBeVisible whether the element should be visible
96
113
  */
97
114
  expectElement(locator: Locator, toBePresent?: boolean, toBeVisible?: boolean): Promise<void>;
115
+ screenshot(opts?: {
116
+ bufferOnly?: boolean;
117
+ name?: string;
118
+ locator?: Locator;
119
+ }): Promise<any>;
120
+ expectScreenshotMatch(locator: Locator, filename?: string, opts?: ScreenshotComparisonOptions): Promise<void>;
98
121
  /**
99
122
  * Waits for a certain amount of time
100
123
  * @param ms number
124
+ * @deprecated use `wait` method instead
101
125
  */
102
126
  waitForTimeout(ms: number): Promise<void>;
103
127
  }
@@ -1,44 +1,53 @@
1
- import { Before, QuickPickleWorld } from 'quickpickle';
1
+ import { Before, VisualWorld } from 'quickpickle';
2
2
  import { defaultsDeep } from 'lodash-es';
3
+ import { commands } from '@vitest/browser/context';
4
+ import { Buffer } from 'buffer';
3
5
 
4
6
  const defaultVitestWorldConfig = {
5
7
  componentDir: '', // directory in which components are kept, relative to project root
6
8
  screenshotDir: 'screenshots', // directory in which to save screenshots, relative to project root (default: "screenshots")
7
- screenshotOptions: {}, // options for the default screenshot comparisons
9
+ screenshotOpts: {
10
+ threshold: 0.1,
11
+ alpha: 0.6,
12
+ maxDiffPercentage: .01,
13
+ },
8
14
  };
9
- class VitestBrowserWorld extends QuickPickleWorld {
15
+ class VitestBrowserWorld extends VisualWorld {
16
+ async render(name, props, renderOptions) { }
17
+ ;
18
+ async cleanup() { }
19
+ ;
10
20
  constructor(context, info) {
11
21
  info = defaultsDeep(info || {}, { config: { worldConfig: defaultVitestWorldConfig } });
12
22
  super(context, info);
23
+ this.actions = {
24
+ clicks: [],
25
+ doubleClicks: [],
26
+ };
13
27
  if (!info.config.worldConfig.screenshotDir && info.config.worldConfig?.screenshotOptions?.customSnapshotsDir) {
14
28
  this.info.config.worldConfig.screenshotDir = info.config.worldConfig.screenshotOptions.customSnapshotsDir;
15
29
  }
16
- this.renderFn = () => { };
17
- this.cleanupFn = () => { };
18
30
  }
19
31
  async init() {
20
32
  let browserContext = await import('@vitest/browser/context');
21
- this.page = browserContext.page;
33
+ this.browserPage = browserContext.page;
22
34
  this.userEvent = browserContext.userEvent;
23
35
  }
24
- async render(name, props, renderOptions) {
25
- let component = typeof name === 'string'
26
- ? await import(`${this.projectRoot}/${this.worldConfig.componentDir}/${name}`.replace(/\/+/g, '/') /* @vite-ignore */)
27
- : name;
28
- await this.renderFn(component, props, renderOptions);
29
- }
30
- ;
31
- async cleanup() {
32
- await this.cleanupFn();
33
- }
34
- sanitizeFilepath(filepath) {
35
- return filepath.replace(/\/\/+/g, '/').replace(/\/[\.~]+\//g, '/');
36
- }
37
- get screenshotDir() {
38
- return this.sanitizeFilepath(`${this.projectRoot}/${this.worldConfig.screenshotDir}`);
36
+ get page() {
37
+ if (!this._page)
38
+ throw new Error('You must render a component before running tests.');
39
+ return this._page;
39
40
  }
40
- get screenshotFilename() {
41
- return `${this.toString().replace(/^.+?Feature: /, '').replace(' ' + this.info.step, '')}.png`;
41
+ set page(value) {
42
+ while (value.parentNode !== null && value.nodeName !== 'BODY')
43
+ value = value.parentNode;
44
+ this._page = this.browserPage.elementLocator(value);
45
+ value.addEventListener('click', (e) => {
46
+ this.actions.clicks.push(e.target);
47
+ });
48
+ value.addEventListener('dblclick', (e) => {
49
+ this.actions.doubleClicks.push(e.target);
50
+ });
42
51
  }
43
52
  /**
44
53
  * Gets a locator based on a certain logic
@@ -109,6 +118,8 @@ class VitestBrowserWorld extends QuickPickleWorld {
109
118
  async scroll(locator, direction, px = 100) {
110
119
  let horiz = direction.includes('t');
111
120
  let el = await locator.element();
121
+ if (el.nodeName === 'BODY' && el.parentElement)
122
+ el = el.parentElement;
112
123
  if (horiz)
113
124
  await el.scrollBy(direction === 'right' ? px : -px, 0);
114
125
  else
@@ -157,9 +168,65 @@ class VitestBrowserWorld extends QuickPickleWorld {
157
168
  if (toBePresent === (matchingElements.length === 0))
158
169
  throw new Error(`The${toBeVisible ? '' : ' hidden'} element "${locator}" was unexpectedly ${toBePresent ? 'not present' : 'present'}.`);
159
170
  }
171
+ async screenshot(opts) {
172
+ let path;
173
+ if (!opts?.bufferOnly)
174
+ path = this.getScreenshotPath(opts?.name);
175
+ let locator = opts?.locator ?? this.page;
176
+ return locator.screenshot({ path });
177
+ }
178
+ async expectScreenshotMatch(locator, filename, opts = {}) {
179
+ const filepath = this.getScreenshotPath(filename);
180
+ let expectedImg;
181
+ /**
182
+ * Load existing screenshot, or save it if it doesn't yet exist
183
+ */
184
+ try {
185
+ expectedImg = await commands.readFile(this.getScreenshotPath(filename), 'base64');
186
+ }
187
+ catch (e) {
188
+ // If the screenshot doesn't exist, save it and pass the test
189
+ await locator.screenshot();
190
+ console.warn(`new visual regression test: ${this.screenshotDir}/${this.screenshotFilename}`);
191
+ return;
192
+ }
193
+ let expected = Buffer.from(expectedImg, 'base64');
194
+ /**
195
+ * Get the screenshot
196
+ */
197
+ // type does not include the "save" option in the docs: see https://vitest.dev/guide/browser/locators#screenshot
198
+ let screenshotOptions = { save: false, base64: true };
199
+ let actualImg = await locator.screenshot(screenshotOptions);
200
+ let actual = Buffer.from(typeof actualImg === 'string' ? actualImg : actualImg.base64, 'base64');
201
+ /**
202
+ * Compare the two screenshots
203
+ */
204
+ let screenshotOpts = defaultsDeep(opts, this.worldConfig.screenshotOpts);
205
+ let matchResult = null;
206
+ try {
207
+ matchResult = await this.screenshotDiff(actual, expected, screenshotOpts);
208
+ }
209
+ catch (e) { }
210
+ console.log({
211
+ pass: matchResult?.pass,
212
+ diffPercentage: matchResult?.diffPercentage,
213
+ filename,
214
+ locator,
215
+ }.toString());
216
+ if (!matchResult?.pass) {
217
+ await commands.writeFile(`${filepath}.actual.png`, actual.toString('base64'), 'base64');
218
+ if (matchResult?.diff)
219
+ await commands.writeFile(`${filepath}.diff.png`, matchResult.diff.toString('base64'), 'base64');
220
+ throw new Error(`Screenshot does not match the snapshot.
221
+ Diff percentage: ${matchResult?.diffPercentage?.toFixed(2) ?? '100'}%
222
+ Max allowed: ${screenshotOpts.maxDiffPercentage}%
223
+ Diffs at: ${filepath}.(diff|actual).png`);
224
+ }
225
+ }
160
226
  /**
161
227
  * Waits for a certain amount of time
162
228
  * @param ms number
229
+ * @deprecated use `wait` method instead
163
230
  */
164
231
  async waitForTimeout(ms) {
165
232
  await new Promise(r => setTimeout(r, ms));