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 +48 -7
- package/dist/index.cjs +24 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -8
- package/dist/index.d.ts +15 -8
- package/dist/index.js +23 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
# Playwright Clipboard Testing
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Effortless clipboard testing for Playwright. Features custom fixtures, smart polling matchers, and auto-managed permissions.
|
|
6
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/playwright-clipboard-testing)
|
|
8
|
+
[](https://www.npmjs.com/package/playwright-clipboard-testing)
|
|
7
9
|
[](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
|
-

|
|
40
|
+
|
|
41
|
+
Clipboard API testing is currently supported in Chromium-based and Firefox browsers.
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
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
|
|
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 (
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
96
|
+
if (browserName === "webkit") {
|
|
84
97
|
throw new Error(
|
|
85
|
-
`[playwright-clipboard] Browser '${browserName}' is not supported. Clipboard
|
|
86
|
-
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":"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","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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
18
|
-
|
|
19
|
-
|
|
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
|
|
65
|
+
if (browserName === "webkit") {
|
|
54
66
|
throw new Error(
|
|
55
|
-
`[playwright-clipboard] Browser '${browserName}' is not supported. Clipboard
|
|
56
|
-
|
|
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
|
|
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