playwright-clipboard-testing 0.1.0 → 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.
package/README.md CHANGED
@@ -2,11 +2,22 @@
2
2
 
3
3
  # Playwright Clipboard Testing
4
4
 
5
- Custom Playwright fixture and matchers for testing clipboard functionality in your web applications.
5
+ Effortless clipboard testing for Playwright. Features custom fixtures, smart polling matchers, and auto-managed permissions.
6
6
 
7
+ [![npm version](https://img.shields.io/npm/v/playwright-clipboard-testing.svg?style=flat&color=blue)](https://www.npmjs.com/package/playwright-clipboard-testing)
8
+ [![npm downloads](https://img.shields.io/npm/dw/playwright-clipboard-testing.svg?color=blue)](https://www.npmjs.com/package/playwright-clipboard-testing)
7
9
  [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg?style=flat&color=blue)](LICENSE)
8
10
 
11
+ ## Why?
12
+ Testing the Clipboard API in Playwright usually requires boilerplate code to manually grant `clipboard-read` and `clipboard-write` permissions for every context, write custom page functions, or deal with tricky async polling issues.
13
+
14
+ **playwright-clipboard-testing** simplifies this with:
15
+ - 🔌 **Zero configuration** — Chromium permissions are granted automatically under the hood (Firefox requires a 1-line config setup).
16
+ - 🔄 **Built-in Auto-retries & Polling** — Uses Playwright's native `expect` polling to wait until the clipboard updates asynchronously.
17
+ - 📦 **TypeScript Ready** — Out-of-the-box support for JSON objects with full type safety (`readJSON<T>()`).
18
+
9
19
  ## Table of Contents
20
+ - [Why?](#why)
10
21
  - [Installation](#installation)
11
22
  - [Browser Support](#browser-support)
12
23
  - [Usage](#usage)
@@ -25,17 +36,39 @@ npm install --save-dev playwright-clipboard-testing
25
36
  ```
26
37
 
27
38
  ## Browser Support
28
- ![NOTE](https://img.shields.io/badge/NOTE-Only%20supported%20in%20Chromium%20browsers-yellow)
39
+ ![NOTE](https://img.shields.io/badge/NOTE-WebKit%20browser%20does%20not%20support%20the%20Clipboard%20API-yellow)
40
+
41
+ Clipboard API testing is currently supported in Chromium-based and Firefox browsers.
29
42
 
30
- The Web Clipboard Permissions API is currently only supported in Chromium-based browsers.
31
- If your Playwright setup runs tests across multiple browsers, skip non-Chromium runs in tests that use the clipboard:
43
+ > **Note:** Permissions are auto-managed out of the box for Chromium. Firefox requires a one-time preference setup in your config.
44
+
45
+ If your Playwright setup runs tests in WebKit, you can skip clipboard tests for that browser:
32
46
  ```ts
33
47
  test('should copy text to clipboard', async ({ page, clipboard, browserName }) => {
34
- test.skip(browserName !== 'chromium', 'Clipboard API is only supported in Chromium');
48
+ test.skip(browserName === 'webkit', 'Clipboard API is only supported in Chromium and Firefox');
35
49
 
36
50
  // test logic...
37
51
  });
38
52
  ```
53
+ If your Playwright setup runs tests in Firefox, manually configure `firefoxUserPrefs` in your Playwright config:
54
+ ```ts
55
+ import { defineConfig, devices } from '@playwright/test';
56
+ import { firefoxClipboardPrefs } from 'playwright-clipboard-testing';
57
+
58
+ export default defineConfig({
59
+ projects: [
60
+ {
61
+ name: 'firefox',
62
+ use: {
63
+ ...devices['Desktop Firefox'],
64
+ launchOptions: {
65
+ firefoxUserPrefs: firefoxClipboardPrefs,
66
+ },
67
+ },
68
+ },
69
+ ],
70
+ });
71
+ ```
39
72
 
40
73
  ## Usage
41
74
  ### Direct Usage
@@ -46,7 +79,7 @@ import { test, expect } from 'playwright-clipboard-testing';
46
79
 
47
80
  test('should copy text to clipboard', async ({ page, clipboard }) => {
48
81
  await page.goto('https://example.com');
49
- await page.click('#copy-button');
82
+ await page.locator('#copy-button').click();
50
83
 
51
84
  await expect(clipboard).toHaveData('Hello, World!');
52
85
  });
@@ -57,13 +90,18 @@ test('should copy text to clipboard', async ({ page, clipboard }) => {
57
90
  If you already have a custom test fixture file, extend Playwright's `test` and `expect` with `clipboardFixture` and `clipboardMatchers`:
58
91
  ```ts
59
92
  import { expect as baseExpect, test as baseTest } from '@playwright/test';
60
- import { type ClipboardHandler, clipboardFixture, clipboardMatchers } from 'playwright-clipboard-testing';
93
+ import {
94
+ type ClipboardHandler,
95
+ clipboardFixture,
96
+ clipboardMatchers,
97
+ } from 'playwright-clipboard-testing';
61
98
 
62
99
  export const test = baseTest.extend<{ clipboard: ClipboardHandler }>({
63
100
  clipboard: clipboardFixture,
64
101
  });
65
102
 
66
103
  export const expect = baseExpect.extend(clipboardMatchers);
104
+
67
105
  ```
68
106
 
69
107
  ## API
@@ -98,3 +136,6 @@ Sergii Oleksenko <serg.oleksenko@gmail.com>
98
136
 
99
137
  ## License
100
138
  [MIT](LICENSE)
139
+
140
+ ---
141
+ If you find this project useful, give it a ⭐️ on [GitHub](https://github.com/sergoleksenko/playwright-clipboard-testing)!
package/dist/index.cjs CHANGED
@@ -24,29 +24,42 @@ __export(index_exports, {
24
24
  clipboardFixture: () => clipboardFixture,
25
25
  clipboardMatchers: () => clipboardMatchers,
26
26
  expect: () => expect2,
27
+ firefoxClipboardPrefs: () => firefoxClipboardPrefs,
27
28
  test: () => test
28
29
  });
29
30
  module.exports = __toCommonJS(index_exports);
30
31
 
32
+ // src/constants.ts
33
+ var firefoxClipboardPrefs = {
34
+ "dom.events.testing.asyncClipboard": true,
35
+ "dom.events.asyncClipboard.readText": true,
36
+ "dom.events.asyncClipboard.writeText": true,
37
+ "permissions.default.clipboard-read": 1,
38
+ "permissions.default.clipboard-write": 1
39
+ };
40
+
31
41
  // src/utils/clipboardHandler.ts
32
42
  var ClipboardHandler = class {
33
- constructor(page, context) {
43
+ constructor(page, context, browserName = "chromium") {
34
44
  this.page = page;
35
45
  this.context = context;
46
+ this.browserName = browserName;
36
47
  this.isPermissionGranted = false;
37
48
  }
38
49
  page;
39
50
  context;
51
+ browserName;
40
52
  isPermissionGranted;
41
53
  /**
42
- * Grants the necessary clipboard permissions ('clipboard-read' and 'clipboard-write') to the browser context.
43
- * This method is called internally before any clipboard read operation to ensure permissions are set.
54
+ * Grants clipboard permissions to the Chromium browser context if not already granted.
44
55
  * @private
45
56
  */
46
57
  async grantPermissions() {
47
- if (!this.isPermissionGranted) {
48
- await this.context.grantPermissions(["clipboard-read", "clipboard-write"]);
49
- this.isPermissionGranted = true;
58
+ if (this.browserName === "chromium") {
59
+ if (!this.isPermissionGranted) {
60
+ await this.context.grantPermissions(["clipboard-read", "clipboard-write"]);
61
+ this.isPermissionGranted = true;
62
+ }
50
63
  }
51
64
  }
52
65
  /**
@@ -80,13 +93,13 @@ var ClipboardHandler = class {
80
93
 
81
94
  // src/fixtures/clipboardFixture.ts
82
95
  var clipboardFixture = async ({ page, context, browserName }, use) => {
83
- if (browserName !== "chromium") {
96
+ if (browserName === "webkit") {
84
97
  throw new Error(
85
- `[playwright-clipboard] Browser '${browserName}' is not supported. Clipboard Permissions API is currently only supported in Chromium-based browsers.
86
- Use test.skip(browserName !== 'chromium') in your tests to skip non-Chromium runs.`
98
+ `[playwright-clipboard] Browser '${browserName}' is not supported. Clipboard API testing is currently supported only in Chromium-based and Firefox browsers.
99
+ Add test.skip(browserName === 'webkit', 'Clipboard API is only supported in Chromium and Firefox'); to your test body.`
87
100
  );
88
101
  }
89
- const handler = new ClipboardHandler(page, context);
102
+ const handler = new ClipboardHandler(page, context, browserName);
90
103
  await use(handler);
91
104
  };
92
105
 
@@ -170,6 +183,7 @@ var expect2 = import_test2.expect.extend({ ...clipboardMatchers });
170
183
  clipboardFixture,
171
184
  clipboardMatchers,
172
185
  expect,
186
+ firefoxClipboardPrefs,
173
187
  test
174
188
  });
175
189
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/clipboardHandler.ts","../src/fixtures/clipboardFixture.ts","../src/fixtures.ts","../src/matchers/toHaveData.ts","../src/utils/matcherUtils.ts","../src/matchers/clipboardMatchers.ts"],"sourcesContent":["export { clipboardFixture } from './fixtures/clipboardFixture.js';\nexport { expect, test } from './fixtures.js';\nexport { clipboardMatchers } from './matchers/clipboardMatchers.js';\nexport { ClipboardHandler } from './utils/clipboardHandler.js';\n","/**\n * Playwright-compatible Clipboard utilities.\n *\n * Core functions for interacting with the browser clipboard within Playwright tests.\n * Provides wrappers for reading plain text and JSON.\n *\n * @remarks\n * These utilities require 'clipboard-read' and 'clipboard-write' permissions\n * to be granted in the browser context.\n */\n\nimport type { BrowserContext, Page } from '@playwright/test';\n\nexport class ClipboardHandler {\n private isPermissionGranted: boolean;\n\n constructor(\n private readonly page: Page,\n private readonly context: BrowserContext,\n ) {\n this.isPermissionGranted = false;\n }\n\n /**\n * Grants the necessary clipboard permissions ('clipboard-read' and 'clipboard-write') to the browser context.\n * This method is called internally before any clipboard read operation to ensure permissions are set.\n * @private\n */\n private async grantPermissions() {\n if (!this.isPermissionGranted) {\n await this.context.grantPermissions(['clipboard-read', 'clipboard-write']);\n this.isPermissionGranted = true;\n }\n }\n\n /**\n * Reads the current text content from the browser clipboard.\n * If the content is a JSON-encoded string (e.g., has extra quotes),\n * it will be returned as is. Use readJSON for automatic parsing.\n *\n * @returns A promise that resolves to the clipboard string content.\n */\n async read(): Promise<string> {\n await this.grantPermissions();\n return await this.page.evaluate(() => navigator.clipboard.readText());\n }\n\n /**\n * Reads the clipboard content and parses it as JSON.\n * If the content is a string literal (e.g., '\"value\"'), it returns the unwrapped string ('value').\n *\n * @template T - The expected type of the parsed JSON object.\n * @returns A promise that resolves to the parsed JSON object of type T.\n * @throws {Error} If the clipboard content is not a valid JSON string.\n */\n async readJSON<T = unknown>(): Promise<T> {\n const text = await this.read();\n try {\n return JSON.parse(text);\n } catch {\n throw new Error(`Clipboard content is not a valid JSON: ${JSON.stringify(text)}`);\n }\n }\n}\n","import type { BrowserContext, Page, TestFixture } from '@playwright/test';\nimport { ClipboardHandler } from '../utils/clipboardHandler.js';\n\n/**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\nexport const clipboardFixture: TestFixture<\n ClipboardHandler,\n { page: Page; context: BrowserContext; browserName: string }\n> = async ({ page, context, browserName }, use) => {\n if (browserName !== 'chromium') {\n throw new Error(\n `[playwright-clipboard] Browser '${browserName}' is not supported. ` +\n 'Clipboard Permissions API is currently only supported in Chromium-based browsers.\\n' +\n `Use test.skip(browserName !== 'chromium') in your tests to skip non-Chromium runs.`,\n );\n }\n\n const handler = new ClipboardHandler(page, context);\n await use(handler);\n};\n","import { expect as baseExpect, test as baseTest } from '@playwright/test';\nimport { clipboardFixture } from './fixtures/clipboardFixture.js';\nimport { clipboardMatchers } from './matchers/clipboardMatchers.js';\nimport type { ClipboardHandler } from './utils/clipboardHandler.js';\n\nexport const test = baseTest.extend<{\n clipboard: ClipboardHandler;\n}>({\n /**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\n clipboard: clipboardFixture,\n});\n\nexport const expect = baseExpect.extend({ ...clipboardMatchers });\n","import { type ExpectMatcherState, expect, type MatcherReturnType } from '@playwright/test';\nimport type { ClipboardHandler } from '../utils/clipboardHandler.js';\nimport { getErrorMessage } from '../utils/matcherUtils.js';\nimport type { MatcherOptions } from './types.js';\n\n/**\n * Asserts that the clipboard content matches the expected value.\n * Uses smart polling to wait for the clipboard to be updated.\n * If the `expected` value is an object, it attempts to parse the clipboard\n * content as JSON before comparing.\n *\n * @this ExpectMatcherState\n * @param clipboard The Clipboard utility instance.\n * @param expected The string or object to compare against the clipboard content.\n * @param options Optional settings for the matcher, such as timeout.\n * @returns A Promise that resolves to a MatcherReturnType object.\n */\nexport async function toHaveData(\n this: ExpectMatcherState,\n clipboard: ClipboardHandler,\n expected: unknown,\n options: MatcherOptions = {},\n) {\n const name = 'toHaveData';\n let pass: boolean;\n let actual: unknown;\n\n const { timeout = 10_000 } = options;\n\n const poll = expect.poll(\n async () => {\n try {\n actual = await clipboard.readJSON();\n } catch {\n actual = await clipboard.read();\n }\n\n if (typeof expected === 'string' && actual !== null && actual !== undefined) {\n return String(actual);\n }\n\n return actual;\n },\n { timeout },\n );\n\n try {\n const expectation = this.isNot ? poll.not : poll;\n await expectation.toEqual(expected);\n pass = true;\n } catch {\n pass = false;\n }\n\n /**\n * NOTE ON INVERSION:\n * In Playwright custom matchers, if `this.isNot` is true, the matcher must return\n * `pass: false` for the assertion to pass. Since `poll.not.toEqual` resolves\n * to `true` when the condition is met, we invert it back to align with Playwright's expectations.\n */\n if (this.isNot) pass = !pass;\n\n const matcherReturn: MatcherReturnType = {\n message: getErrorMessage.call(this, name, expected, actual),\n pass,\n name,\n expected,\n actual,\n };\n\n return matcherReturn;\n}\n","import type { ExpectMatcherState } from '@playwright/test';\n\n/**\n * Generates an error message for a custom matcher.\n *\n * @this ExpectMatcherState\n * @param name The name of the matcher.\n * @param expected The expected value.\n * @param actual The actual value.\n * @param error An optional error object.\n * @param errorMessage An optional error message string.\n * @returns A function that returns the error message string.\n */\nexport function getErrorMessage(\n this: ExpectMatcherState,\n name: string,\n expected: unknown,\n actual: unknown,\n error?: Error | null,\n errorMessage?: string | undefined | null,\n): () => string {\n const message = `${this.utils.matcherHint(name, undefined, undefined, { isNot: this.isNot })}\\n\\n`;\n\n if (error) {\n return () => `${message}An unexpected error occurred:\\n${error.message}`;\n }\n\n if (errorMessage) {\n return () => `${message}${errorMessage}`;\n }\n\n return () =>\n message +\n `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(expected)}\\n` +\n `Received: ${this.utils.printReceived(actual)}`;\n}\n","import { toHaveData } from './toHaveData.js';\n\n/**\n * Export an object containing all the custom clipboard matchers for Playwright.\n */\nexport const clipboardMatchers = {\n toHaveData,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAAAA;AAAA,EAAA;AAAA;AAAA;;;ACaO,IAAM,mBAAN,MAAuB;AAAA,EAG5B,YACmB,MACA,SACjB;AAFiB;AACA;AAEjB,SAAK,sBAAsB;AAAA,EAC7B;AAAA,EAJmB;AAAA,EACA;AAAA,EAJX;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcR,MAAc,mBAAmB;AAC/B,QAAI,CAAC,KAAK,qBAAqB;AAC7B,YAAM,KAAK,QAAQ,iBAAiB,CAAC,kBAAkB,iBAAiB,CAAC;AACzE,WAAK,sBAAsB;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAwB;AAC5B,UAAM,KAAK,iBAAiB;AAC5B,WAAO,MAAM,KAAK,KAAK,SAAS,MAAM,UAAU,UAAU,SAAS,CAAC;AAAA,EACtE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,WAAoC;AACxC,UAAM,OAAO,MAAM,KAAK,KAAK;AAC7B,QAAI;AACF,aAAO,KAAK,MAAM,IAAI;AAAA,IACxB,QAAQ;AACN,YAAM,IAAI,MAAM,0CAA0C,KAAK,UAAU,IAAI,CAAC,EAAE;AAAA,IAClF;AAAA,EACF;AACF;;;ACxDO,IAAM,mBAGT,OAAO,EAAE,MAAM,SAAS,YAAY,GAAG,QAAQ;AACjD,MAAI,gBAAgB,YAAY;AAC9B,UAAM,IAAI;AAAA,MACR,mCAAmC,WAAW;AAAA;AAAA,IAGhD;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,iBAAiB,MAAM,OAAO;AAClD,QAAM,IAAI,OAAO;AACnB;;;ACrBA,IAAAC,eAAuD;;;ACAvD,kBAAwE;;;ACajE,SAAS,gBAEd,MACA,UACA,QACA,OACA,cACc;AACd,QAAM,UAAU,GAAG,KAAK,MAAM,YAAY,MAAM,QAAW,QAAW,EAAE,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA;AAAA;AAE5F,MAAI,OAAO;AACT,WAAO,MAAM,GAAG,OAAO;AAAA,EAAkC,MAAM,OAAO;AAAA,EACxE;AAEA,MAAI,cAAc;AAChB,WAAO,MAAM,GAAG,OAAO,GAAG,YAAY;AAAA,EACxC;AAEA,SAAO,MACL,UACA,aAAa,KAAK,QAAQ,SAAS,EAAE,GAAG,KAAK,MAAM,cAAc,QAAQ,CAAC;AAAA,YAC7D,KAAK,MAAM,cAAc,MAAM,CAAC;AACjD;;;ADlBA,eAAsB,WAEpB,WACA,UACA,UAA0B,CAAC,GAC3B;AACA,QAAM,OAAO;AACb,MAAI;AACJ,MAAI;AAEJ,QAAM,EAAE,UAAU,IAAO,IAAI;AAE7B,QAAM,OAAO,mBAAO;AAAA,IAClB,YAAY;AACV,UAAI;AACF,iBAAS,MAAM,UAAU,SAAS;AAAA,MACpC,QAAQ;AACN,iBAAS,MAAM,UAAU,KAAK;AAAA,MAChC;AAEA,UAAI,OAAO,aAAa,YAAY,WAAW,QAAQ,WAAW,QAAW;AAC3E,eAAO,OAAO,MAAM;AAAA,MACtB;AAEA,aAAO;AAAA,IACT;AAAA,IACA,EAAE,QAAQ;AAAA,EACZ;AAEA,MAAI;AACF,UAAM,cAAc,KAAK,QAAQ,KAAK,MAAM;AAC5C,UAAM,YAAY,QAAQ,QAAQ;AAClC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AAQA,MAAI,KAAK,MAAO,QAAO,CAAC;AAExB,QAAM,gBAAmC;AAAA,IACvC,SAAS,gBAAgB,KAAK,MAAM,MAAM,UAAU,MAAM;AAAA,IAC1D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;;;AElEO,IAAM,oBAAoB;AAAA,EAC/B;AACF;;;AHFO,IAAM,OAAO,aAAAC,KAAS,OAE1B;AAAA;AAAA;AAAA;AAAA;AAAA,EAKD,WAAW;AACb,CAAC;AAEM,IAAMC,UAAS,aAAAC,OAAW,OAAO,EAAE,GAAG,kBAAkB,CAAC;","names":["expect","import_test","baseTest","expect","baseExpect"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/utils/clipboardHandler.ts","../src/fixtures/clipboardFixture.ts","../src/fixtures.ts","../src/matchers/toHaveData.ts","../src/utils/matcherUtils.ts","../src/matchers/clipboardMatchers.ts"],"sourcesContent":["export { firefoxClipboardPrefs } from './constants.js';\nexport { clipboardFixture } from './fixtures/clipboardFixture.js';\nexport { expect, test } from './fixtures.js';\nexport { clipboardMatchers } from './matchers/clipboardMatchers.js';\nexport { ClipboardHandler } from './utils/clipboardHandler.js';\n","export const firefoxClipboardPrefs = {\n 'dom.events.testing.asyncClipboard': true,\n 'dom.events.asyncClipboard.readText': true,\n 'dom.events.asyncClipboard.writeText': true,\n 'permissions.default.clipboard-read': 1,\n 'permissions.default.clipboard-write': 1,\n};\n","/**\n * Playwright-compatible Clipboard utilities.\n *\n * Core functions for interacting with the browser clipboard within Playwright tests.\n * Provides wrappers for reading plain text and JSON.\n *\n * @remarks\n * These utilities require 'clipboard-read' and 'clipboard-write' permissions\n * to be granted in the browser context.\n */\n\nimport type { BrowserContext, Page } from '@playwright/test';\nimport type { BrowserName } from '../types.js';\n\nexport class ClipboardHandler {\n private readonly browserName: BrowserName;\n private isPermissionGranted: boolean;\n\n constructor(\n private readonly page: Page,\n private readonly context: BrowserContext,\n browserName: BrowserName = 'chromium',\n ) {\n this.browserName = browserName;\n this.isPermissionGranted = false;\n }\n\n /**\n * Grants clipboard permissions to the Chromium browser context if not already granted.\n * @private\n */\n private async grantPermissions() {\n if (this.browserName === 'chromium') {\n if (!this.isPermissionGranted) {\n await this.context.grantPermissions(['clipboard-read', 'clipboard-write']);\n this.isPermissionGranted = true;\n }\n }\n }\n\n /**\n * Reads the current text content from the browser clipboard.\n * If the content is a JSON-encoded string (e.g., has extra quotes),\n * it will be returned as is. Use readJSON for automatic parsing.\n *\n * @returns A promise that resolves to the clipboard string content.\n */\n async read(): Promise<string> {\n await this.grantPermissions();\n return await this.page.evaluate(() => navigator.clipboard.readText());\n }\n\n /**\n * Reads the clipboard content and parses it as JSON.\n * If the content is a string literal (e.g., '\"value\"'), it returns the unwrapped string ('value').\n *\n * @template T - The expected type of the parsed JSON object.\n * @returns A promise that resolves to the parsed JSON object of type T.\n * @throws {Error} If the clipboard content is not a valid JSON string.\n */\n async readJSON<T = unknown>(): Promise<T> {\n const text = await this.read();\n try {\n return JSON.parse(text);\n } catch {\n throw new Error(`Clipboard content is not a valid JSON: ${JSON.stringify(text)}`);\n }\n }\n}\n","import type { BrowserContext, Page, TestFixture } from '@playwright/test';\nimport type { BrowserName } from '../types.js';\nimport { ClipboardHandler } from '../utils/clipboardHandler.js';\n\n/**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\nexport const clipboardFixture: TestFixture<\n ClipboardHandler,\n { page: Page; context: BrowserContext; browserName: BrowserName }\n> = async ({ page, context, browserName }, use) => {\n if (browserName === 'webkit') {\n throw new Error(\n `[playwright-clipboard] Browser '${browserName}' is not supported. ` +\n 'Clipboard API testing is currently supported only in Chromium-based and Firefox browsers.\\n' +\n `Add test.skip(browserName === 'webkit', 'Clipboard API is only supported in Chromium and Firefox'); to your test body.`,\n );\n }\n\n const handler = new ClipboardHandler(page, context, browserName);\n await use(handler);\n};\n","import { expect as baseExpect, test as baseTest } from '@playwright/test';\nimport { clipboardFixture } from './fixtures/clipboardFixture.js';\nimport { clipboardMatchers } from './matchers/clipboardMatchers.js';\nimport type { ClipboardHandler } from './utils/clipboardHandler.js';\n\nexport const test = baseTest.extend<{\n clipboard: ClipboardHandler;\n}>({\n /**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\n clipboard: clipboardFixture,\n});\n\nexport const expect = baseExpect.extend({ ...clipboardMatchers });\n","import { type ExpectMatcherState, expect, type MatcherReturnType } from '@playwright/test';\nimport type { ClipboardHandler } from '../utils/clipboardHandler.js';\nimport { getErrorMessage } from '../utils/matcherUtils.js';\nimport type { MatcherOptions } from './types.js';\n\n/**\n * Asserts that the clipboard content matches the expected value.\n *\n * @this ExpectMatcherState\n * @param clipboard The Clipboard utility instance.\n * @param expected The string or object to compare against the clipboard content.\n * @param options Optional settings for the matcher, such as timeout.\n * @returns A Promise that resolves to a MatcherReturnType object.\n */\nexport async function toHaveData(\n this: ExpectMatcherState,\n clipboard: ClipboardHandler,\n expected: unknown,\n options: MatcherOptions = {},\n) {\n const name = 'toHaveData';\n let pass: boolean;\n let actual: unknown;\n\n const { timeout = 10_000 } = options;\n\n const poll = expect.poll(\n async () => {\n try {\n actual = await clipboard.readJSON();\n } catch {\n actual = await clipboard.read();\n }\n\n if (typeof expected === 'string' && actual !== null && actual !== undefined) {\n return String(actual);\n }\n\n return actual;\n },\n { timeout },\n );\n\n try {\n const expectation = this.isNot ? poll.not : poll;\n await expectation.toEqual(expected);\n pass = true;\n } catch {\n pass = false;\n }\n\n if (this.isNot) pass = !pass;\n\n const matcherReturn: MatcherReturnType = {\n message: getErrorMessage.call(this, name, expected, actual),\n pass,\n name,\n expected,\n actual,\n };\n\n return matcherReturn;\n}\n","import type { ExpectMatcherState } from '@playwright/test';\n\n/**\n * Generates an error message for a custom matcher.\n *\n * @this ExpectMatcherState\n * @param name The name of the matcher.\n * @param expected The expected value.\n * @param actual The actual value.\n * @param error An optional error object.\n * @param errorMessage An optional error message string.\n * @returns A function that returns the error message string.\n */\nexport function getErrorMessage(\n this: ExpectMatcherState,\n name: string,\n expected: unknown,\n actual: unknown,\n error?: Error | null,\n errorMessage?: string | undefined | null,\n): () => string {\n const message = `${this.utils.matcherHint(name, undefined, undefined, { isNot: this.isNot })}\\n\\n`;\n\n if (error) {\n return () => `${message}An unexpected error occurred:\\n${error.message}`;\n }\n\n if (errorMessage) {\n return () => `${message}${errorMessage}`;\n }\n\n return () =>\n message +\n `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(expected)}\\n` +\n `Received: ${this.utils.printReceived(actual)}`;\n}\n","import { toHaveData } from './toHaveData.js';\n\n/**\n * Export an object containing all the custom clipboard matchers for Playwright.\n */\nexport const clipboardMatchers = {\n toHaveData,\n};\n"],"mappings":"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","names":["expect","import_test","baseTest","expect","baseExpect"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,16 @@
1
1
  import * as _playwright_test from '@playwright/test';
2
2
  import { Page, BrowserContext, TestFixture, ExpectMatcherState, MatcherReturnType } from '@playwright/test';
3
3
 
4
+ declare const firefoxClipboardPrefs: {
5
+ 'dom.events.testing.asyncClipboard': boolean;
6
+ 'dom.events.asyncClipboard.readText': boolean;
7
+ 'dom.events.asyncClipboard.writeText': boolean;
8
+ 'permissions.default.clipboard-read': number;
9
+ 'permissions.default.clipboard-write': number;
10
+ };
11
+
12
+ type BrowserName = 'chromium' | 'firefox' | 'webkit';
13
+
4
14
  /**
5
15
  * Playwright-compatible Clipboard utilities.
6
16
  *
@@ -15,11 +25,11 @@ import { Page, BrowserContext, TestFixture, ExpectMatcherState, MatcherReturnTyp
15
25
  declare class ClipboardHandler {
16
26
  private readonly page;
17
27
  private readonly context;
28
+ private readonly browserName;
18
29
  private isPermissionGranted;
19
- constructor(page: Page, context: BrowserContext);
30
+ constructor(page: Page, context: BrowserContext, browserName?: BrowserName);
20
31
  /**
21
- * Grants the necessary clipboard permissions ('clipboard-read' and 'clipboard-write') to the browser context.
22
- * This method is called internally before any clipboard read operation to ensure permissions are set.
32
+ * Grants clipboard permissions to the Chromium browser context if not already granted.
23
33
  * @private
24
34
  */
25
35
  private grantPermissions;
@@ -49,7 +59,7 @@ declare class ClipboardHandler {
49
59
  declare const clipboardFixture: TestFixture<ClipboardHandler, {
50
60
  page: Page;
51
61
  context: BrowserContext;
52
- browserName: string;
62
+ browserName: BrowserName;
53
63
  }>;
54
64
 
55
65
  type MatcherOptions = {
@@ -79,9 +89,6 @@ declare global {
79
89
 
80
90
  /**
81
91
  * Asserts that the clipboard content matches the expected value.
82
- * Uses smart polling to wait for the clipboard to be updated.
83
- * If the `expected` value is an object, it attempts to parse the clipboard
84
- * content as JSON before comparing.
85
92
  *
86
93
  * @this ExpectMatcherState
87
94
  * @param clipboard The Clipboard utility instance.
@@ -105,4 +112,4 @@ declare const clipboardMatchers: {
105
112
  toHaveData: typeof toHaveData;
106
113
  };
107
114
 
108
- export { ClipboardHandler, clipboardFixture, clipboardMatchers, expect, test };
115
+ export { ClipboardHandler, clipboardFixture, clipboardMatchers, expect, firefoxClipboardPrefs, test };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,16 @@
1
1
  import * as _playwright_test from '@playwright/test';
2
2
  import { Page, BrowserContext, TestFixture, ExpectMatcherState, MatcherReturnType } from '@playwright/test';
3
3
 
4
+ declare const firefoxClipboardPrefs: {
5
+ 'dom.events.testing.asyncClipboard': boolean;
6
+ 'dom.events.asyncClipboard.readText': boolean;
7
+ 'dom.events.asyncClipboard.writeText': boolean;
8
+ 'permissions.default.clipboard-read': number;
9
+ 'permissions.default.clipboard-write': number;
10
+ };
11
+
12
+ type BrowserName = 'chromium' | 'firefox' | 'webkit';
13
+
4
14
  /**
5
15
  * Playwright-compatible Clipboard utilities.
6
16
  *
@@ -15,11 +25,11 @@ import { Page, BrowserContext, TestFixture, ExpectMatcherState, MatcherReturnTyp
15
25
  declare class ClipboardHandler {
16
26
  private readonly page;
17
27
  private readonly context;
28
+ private readonly browserName;
18
29
  private isPermissionGranted;
19
- constructor(page: Page, context: BrowserContext);
30
+ constructor(page: Page, context: BrowserContext, browserName?: BrowserName);
20
31
  /**
21
- * Grants the necessary clipboard permissions ('clipboard-read' and 'clipboard-write') to the browser context.
22
- * This method is called internally before any clipboard read operation to ensure permissions are set.
32
+ * Grants clipboard permissions to the Chromium browser context if not already granted.
23
33
  * @private
24
34
  */
25
35
  private grantPermissions;
@@ -49,7 +59,7 @@ declare class ClipboardHandler {
49
59
  declare const clipboardFixture: TestFixture<ClipboardHandler, {
50
60
  page: Page;
51
61
  context: BrowserContext;
52
- browserName: string;
62
+ browserName: BrowserName;
53
63
  }>;
54
64
 
55
65
  type MatcherOptions = {
@@ -79,9 +89,6 @@ declare global {
79
89
 
80
90
  /**
81
91
  * Asserts that the clipboard content matches the expected value.
82
- * Uses smart polling to wait for the clipboard to be updated.
83
- * If the `expected` value is an object, it attempts to parse the clipboard
84
- * content as JSON before comparing.
85
92
  *
86
93
  * @this ExpectMatcherState
87
94
  * @param clipboard The Clipboard utility instance.
@@ -105,4 +112,4 @@ declare const clipboardMatchers: {
105
112
  toHaveData: typeof toHaveData;
106
113
  };
107
114
 
108
- export { ClipboardHandler, clipboardFixture, clipboardMatchers, expect, test };
115
+ export { ClipboardHandler, clipboardFixture, clipboardMatchers, expect, firefoxClipboardPrefs, test };
package/dist/index.js CHANGED
@@ -1,22 +1,34 @@
1
+ // src/constants.ts
2
+ var firefoxClipboardPrefs = {
3
+ "dom.events.testing.asyncClipboard": true,
4
+ "dom.events.asyncClipboard.readText": true,
5
+ "dom.events.asyncClipboard.writeText": true,
6
+ "permissions.default.clipboard-read": 1,
7
+ "permissions.default.clipboard-write": 1
8
+ };
9
+
1
10
  // src/utils/clipboardHandler.ts
2
11
  var ClipboardHandler = class {
3
- constructor(page, context) {
12
+ constructor(page, context, browserName = "chromium") {
4
13
  this.page = page;
5
14
  this.context = context;
15
+ this.browserName = browserName;
6
16
  this.isPermissionGranted = false;
7
17
  }
8
18
  page;
9
19
  context;
20
+ browserName;
10
21
  isPermissionGranted;
11
22
  /**
12
- * Grants the necessary clipboard permissions ('clipboard-read' and 'clipboard-write') to the browser context.
13
- * This method is called internally before any clipboard read operation to ensure permissions are set.
23
+ * Grants clipboard permissions to the Chromium browser context if not already granted.
14
24
  * @private
15
25
  */
16
26
  async grantPermissions() {
17
- if (!this.isPermissionGranted) {
18
- await this.context.grantPermissions(["clipboard-read", "clipboard-write"]);
19
- this.isPermissionGranted = true;
27
+ if (this.browserName === "chromium") {
28
+ if (!this.isPermissionGranted) {
29
+ await this.context.grantPermissions(["clipboard-read", "clipboard-write"]);
30
+ this.isPermissionGranted = true;
31
+ }
20
32
  }
21
33
  }
22
34
  /**
@@ -50,13 +62,13 @@ var ClipboardHandler = class {
50
62
 
51
63
  // src/fixtures/clipboardFixture.ts
52
64
  var clipboardFixture = async ({ page, context, browserName }, use) => {
53
- if (browserName !== "chromium") {
65
+ if (browserName === "webkit") {
54
66
  throw new Error(
55
- `[playwright-clipboard] Browser '${browserName}' is not supported. Clipboard Permissions API is currently only supported in Chromium-based browsers.
56
- Use test.skip(browserName !== 'chromium') in your tests to skip non-Chromium runs.`
67
+ `[playwright-clipboard] Browser '${browserName}' is not supported. Clipboard API testing is currently supported only in Chromium-based and Firefox browsers.
68
+ Add test.skip(browserName === 'webkit', 'Clipboard API is only supported in Chromium and Firefox'); to your test body.`
57
69
  );
58
70
  }
59
- const handler = new ClipboardHandler(page, context);
71
+ const handler = new ClipboardHandler(page, context, browserName);
60
72
  await use(handler);
61
73
  };
62
74
 
@@ -139,6 +151,7 @@ export {
139
151
  clipboardFixture,
140
152
  clipboardMatchers,
141
153
  expect2 as expect,
154
+ firefoxClipboardPrefs,
142
155
  test
143
156
  };
144
157
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/clipboardHandler.ts","../src/fixtures/clipboardFixture.ts","../src/fixtures.ts","../src/matchers/toHaveData.ts","../src/utils/matcherUtils.ts","../src/matchers/clipboardMatchers.ts"],"sourcesContent":["/**\n * Playwright-compatible Clipboard utilities.\n *\n * Core functions for interacting with the browser clipboard within Playwright tests.\n * Provides wrappers for reading plain text and JSON.\n *\n * @remarks\n * These utilities require 'clipboard-read' and 'clipboard-write' permissions\n * to be granted in the browser context.\n */\n\nimport type { BrowserContext, Page } from '@playwright/test';\n\nexport class ClipboardHandler {\n private isPermissionGranted: boolean;\n\n constructor(\n private readonly page: Page,\n private readonly context: BrowserContext,\n ) {\n this.isPermissionGranted = false;\n }\n\n /**\n * Grants the necessary clipboard permissions ('clipboard-read' and 'clipboard-write') to the browser context.\n * This method is called internally before any clipboard read operation to ensure permissions are set.\n * @private\n */\n private async grantPermissions() {\n if (!this.isPermissionGranted) {\n await this.context.grantPermissions(['clipboard-read', 'clipboard-write']);\n this.isPermissionGranted = true;\n }\n }\n\n /**\n * Reads the current text content from the browser clipboard.\n * If the content is a JSON-encoded string (e.g., has extra quotes),\n * it will be returned as is. Use readJSON for automatic parsing.\n *\n * @returns A promise that resolves to the clipboard string content.\n */\n async read(): Promise<string> {\n await this.grantPermissions();\n return await this.page.evaluate(() => navigator.clipboard.readText());\n }\n\n /**\n * Reads the clipboard content and parses it as JSON.\n * If the content is a string literal (e.g., '\"value\"'), it returns the unwrapped string ('value').\n *\n * @template T - The expected type of the parsed JSON object.\n * @returns A promise that resolves to the parsed JSON object of type T.\n * @throws {Error} If the clipboard content is not a valid JSON string.\n */\n async readJSON<T = unknown>(): Promise<T> {\n const text = await this.read();\n try {\n return JSON.parse(text);\n } catch {\n throw new Error(`Clipboard content is not a valid JSON: ${JSON.stringify(text)}`);\n }\n }\n}\n","import type { BrowserContext, Page, TestFixture } from '@playwright/test';\nimport { ClipboardHandler } from '../utils/clipboardHandler.js';\n\n/**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\nexport const clipboardFixture: TestFixture<\n ClipboardHandler,\n { page: Page; context: BrowserContext; browserName: string }\n> = async ({ page, context, browserName }, use) => {\n if (browserName !== 'chromium') {\n throw new Error(\n `[playwright-clipboard] Browser '${browserName}' is not supported. ` +\n 'Clipboard Permissions API is currently only supported in Chromium-based browsers.\\n' +\n `Use test.skip(browserName !== 'chromium') in your tests to skip non-Chromium runs.`,\n );\n }\n\n const handler = new ClipboardHandler(page, context);\n await use(handler);\n};\n","import { expect as baseExpect, test as baseTest } from '@playwright/test';\nimport { clipboardFixture } from './fixtures/clipboardFixture.js';\nimport { clipboardMatchers } from './matchers/clipboardMatchers.js';\nimport type { ClipboardHandler } from './utils/clipboardHandler.js';\n\nexport const test = baseTest.extend<{\n clipboard: ClipboardHandler;\n}>({\n /**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\n clipboard: clipboardFixture,\n});\n\nexport const expect = baseExpect.extend({ ...clipboardMatchers });\n","import { type ExpectMatcherState, expect, type MatcherReturnType } from '@playwright/test';\nimport type { ClipboardHandler } from '../utils/clipboardHandler.js';\nimport { getErrorMessage } from '../utils/matcherUtils.js';\nimport type { MatcherOptions } from './types.js';\n\n/**\n * Asserts that the clipboard content matches the expected value.\n * Uses smart polling to wait for the clipboard to be updated.\n * If the `expected` value is an object, it attempts to parse the clipboard\n * content as JSON before comparing.\n *\n * @this ExpectMatcherState\n * @param clipboard The Clipboard utility instance.\n * @param expected The string or object to compare against the clipboard content.\n * @param options Optional settings for the matcher, such as timeout.\n * @returns A Promise that resolves to a MatcherReturnType object.\n */\nexport async function toHaveData(\n this: ExpectMatcherState,\n clipboard: ClipboardHandler,\n expected: unknown,\n options: MatcherOptions = {},\n) {\n const name = 'toHaveData';\n let pass: boolean;\n let actual: unknown;\n\n const { timeout = 10_000 } = options;\n\n const poll = expect.poll(\n async () => {\n try {\n actual = await clipboard.readJSON();\n } catch {\n actual = await clipboard.read();\n }\n\n if (typeof expected === 'string' && actual !== null && actual !== undefined) {\n return String(actual);\n }\n\n return actual;\n },\n { timeout },\n );\n\n try {\n const expectation = this.isNot ? poll.not : poll;\n await expectation.toEqual(expected);\n pass = true;\n } catch {\n pass = false;\n }\n\n /**\n * NOTE ON INVERSION:\n * In Playwright custom matchers, if `this.isNot` is true, the matcher must return\n * `pass: false` for the assertion to pass. Since `poll.not.toEqual` resolves\n * to `true` when the condition is met, we invert it back to align with Playwright's expectations.\n */\n if (this.isNot) pass = !pass;\n\n const matcherReturn: MatcherReturnType = {\n message: getErrorMessage.call(this, name, expected, actual),\n pass,\n name,\n expected,\n actual,\n };\n\n return matcherReturn;\n}\n","import type { ExpectMatcherState } from '@playwright/test';\n\n/**\n * Generates an error message for a custom matcher.\n *\n * @this ExpectMatcherState\n * @param name The name of the matcher.\n * @param expected The expected value.\n * @param actual The actual value.\n * @param error An optional error object.\n * @param errorMessage An optional error message string.\n * @returns A function that returns the error message string.\n */\nexport function getErrorMessage(\n this: ExpectMatcherState,\n name: string,\n expected: unknown,\n actual: unknown,\n error?: Error | null,\n errorMessage?: string | undefined | null,\n): () => string {\n const message = `${this.utils.matcherHint(name, undefined, undefined, { isNot: this.isNot })}\\n\\n`;\n\n if (error) {\n return () => `${message}An unexpected error occurred:\\n${error.message}`;\n }\n\n if (errorMessage) {\n return () => `${message}${errorMessage}`;\n }\n\n return () =>\n message +\n `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(expected)}\\n` +\n `Received: ${this.utils.printReceived(actual)}`;\n}\n","import { toHaveData } from './toHaveData.js';\n\n/**\n * Export an object containing all the custom clipboard matchers for Playwright.\n */\nexport const clipboardMatchers = {\n toHaveData,\n};\n"],"mappings":"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","names":["expect"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/utils/clipboardHandler.ts","../src/fixtures/clipboardFixture.ts","../src/fixtures.ts","../src/matchers/toHaveData.ts","../src/utils/matcherUtils.ts","../src/matchers/clipboardMatchers.ts"],"sourcesContent":["export const firefoxClipboardPrefs = {\n 'dom.events.testing.asyncClipboard': true,\n 'dom.events.asyncClipboard.readText': true,\n 'dom.events.asyncClipboard.writeText': true,\n 'permissions.default.clipboard-read': 1,\n 'permissions.default.clipboard-write': 1,\n};\n","/**\n * Playwright-compatible Clipboard utilities.\n *\n * Core functions for interacting with the browser clipboard within Playwright tests.\n * Provides wrappers for reading plain text and JSON.\n *\n * @remarks\n * These utilities require 'clipboard-read' and 'clipboard-write' permissions\n * to be granted in the browser context.\n */\n\nimport type { BrowserContext, Page } from '@playwright/test';\nimport type { BrowserName } from '../types.js';\n\nexport class ClipboardHandler {\n private readonly browserName: BrowserName;\n private isPermissionGranted: boolean;\n\n constructor(\n private readonly page: Page,\n private readonly context: BrowserContext,\n browserName: BrowserName = 'chromium',\n ) {\n this.browserName = browserName;\n this.isPermissionGranted = false;\n }\n\n /**\n * Grants clipboard permissions to the Chromium browser context if not already granted.\n * @private\n */\n private async grantPermissions() {\n if (this.browserName === 'chromium') {\n if (!this.isPermissionGranted) {\n await this.context.grantPermissions(['clipboard-read', 'clipboard-write']);\n this.isPermissionGranted = true;\n }\n }\n }\n\n /**\n * Reads the current text content from the browser clipboard.\n * If the content is a JSON-encoded string (e.g., has extra quotes),\n * it will be returned as is. Use readJSON for automatic parsing.\n *\n * @returns A promise that resolves to the clipboard string content.\n */\n async read(): Promise<string> {\n await this.grantPermissions();\n return await this.page.evaluate(() => navigator.clipboard.readText());\n }\n\n /**\n * Reads the clipboard content and parses it as JSON.\n * If the content is a string literal (e.g., '\"value\"'), it returns the unwrapped string ('value').\n *\n * @template T - The expected type of the parsed JSON object.\n * @returns A promise that resolves to the parsed JSON object of type T.\n * @throws {Error} If the clipboard content is not a valid JSON string.\n */\n async readJSON<T = unknown>(): Promise<T> {\n const text = await this.read();\n try {\n return JSON.parse(text);\n } catch {\n throw new Error(`Clipboard content is not a valid JSON: ${JSON.stringify(text)}`);\n }\n }\n}\n","import type { BrowserContext, Page, TestFixture } from '@playwright/test';\nimport type { BrowserName } from '../types.js';\nimport { ClipboardHandler } from '../utils/clipboardHandler.js';\n\n/**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\nexport const clipboardFixture: TestFixture<\n ClipboardHandler,\n { page: Page; context: BrowserContext; browserName: BrowserName }\n> = async ({ page, context, browserName }, use) => {\n if (browserName === 'webkit') {\n throw new Error(\n `[playwright-clipboard] Browser '${browserName}' is not supported. ` +\n 'Clipboard API testing is currently supported only in Chromium-based and Firefox browsers.\\n' +\n `Add test.skip(browserName === 'webkit', 'Clipboard API is only supported in Chromium and Firefox'); to your test body.`,\n );\n }\n\n const handler = new ClipboardHandler(page, context, browserName);\n await use(handler);\n};\n","import { expect as baseExpect, test as baseTest } from '@playwright/test';\nimport { clipboardFixture } from './fixtures/clipboardFixture.js';\nimport { clipboardMatchers } from './matchers/clipboardMatchers.js';\nimport type { ClipboardHandler } from './utils/clipboardHandler.js';\n\nexport const test = baseTest.extend<{\n clipboard: ClipboardHandler;\n}>({\n /**\n * A fixture that provides an instance of the Clipboard utility for interacting with the system clipboard.\n * It allows reading from the clipboard during tests.\n */\n clipboard: clipboardFixture,\n});\n\nexport const expect = baseExpect.extend({ ...clipboardMatchers });\n","import { type ExpectMatcherState, expect, type MatcherReturnType } from '@playwright/test';\nimport type { ClipboardHandler } from '../utils/clipboardHandler.js';\nimport { getErrorMessage } from '../utils/matcherUtils.js';\nimport type { MatcherOptions } from './types.js';\n\n/**\n * Asserts that the clipboard content matches the expected value.\n *\n * @this ExpectMatcherState\n * @param clipboard The Clipboard utility instance.\n * @param expected The string or object to compare against the clipboard content.\n * @param options Optional settings for the matcher, such as timeout.\n * @returns A Promise that resolves to a MatcherReturnType object.\n */\nexport async function toHaveData(\n this: ExpectMatcherState,\n clipboard: ClipboardHandler,\n expected: unknown,\n options: MatcherOptions = {},\n) {\n const name = 'toHaveData';\n let pass: boolean;\n let actual: unknown;\n\n const { timeout = 10_000 } = options;\n\n const poll = expect.poll(\n async () => {\n try {\n actual = await clipboard.readJSON();\n } catch {\n actual = await clipboard.read();\n }\n\n if (typeof expected === 'string' && actual !== null && actual !== undefined) {\n return String(actual);\n }\n\n return actual;\n },\n { timeout },\n );\n\n try {\n const expectation = this.isNot ? poll.not : poll;\n await expectation.toEqual(expected);\n pass = true;\n } catch {\n pass = false;\n }\n\n if (this.isNot) pass = !pass;\n\n const matcherReturn: MatcherReturnType = {\n message: getErrorMessage.call(this, name, expected, actual),\n pass,\n name,\n expected,\n actual,\n };\n\n return matcherReturn;\n}\n","import type { ExpectMatcherState } from '@playwright/test';\n\n/**\n * Generates an error message for a custom matcher.\n *\n * @this ExpectMatcherState\n * @param name The name of the matcher.\n * @param expected The expected value.\n * @param actual The actual value.\n * @param error An optional error object.\n * @param errorMessage An optional error message string.\n * @returns A function that returns the error message string.\n */\nexport function getErrorMessage(\n this: ExpectMatcherState,\n name: string,\n expected: unknown,\n actual: unknown,\n error?: Error | null,\n errorMessage?: string | undefined | null,\n): () => string {\n const message = `${this.utils.matcherHint(name, undefined, undefined, { isNot: this.isNot })}\\n\\n`;\n\n if (error) {\n return () => `${message}An unexpected error occurred:\\n${error.message}`;\n }\n\n if (errorMessage) {\n return () => `${message}${errorMessage}`;\n }\n\n return () =>\n message +\n `Expected: ${this.isNot ? 'not ' : ''}${this.utils.printExpected(expected)}\\n` +\n `Received: ${this.utils.printReceived(actual)}`;\n}\n","import { toHaveData } from './toHaveData.js';\n\n/**\n * Export an object containing all the custom clipboard matchers for Playwright.\n */\nexport const clipboardMatchers = {\n toHaveData,\n};\n"],"mappings":"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","names":["expect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playwright-clipboard-testing",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Playwright fixture and custom matchers for clipboard testing and assertions.",
5
5
  "author": "Sergii Oleksenko <serg.oleksenko@gmail.com>",
6
6
  "license": "MIT",