@avalix/chroma 0.0.16 → 1.0.1

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
@@ -1,6 +1,6 @@
1
1
  # @avalix/chroma
2
2
 
3
- End-to-end testing library for Polkadot wallet interactions using Playwright.
3
+ End-to-end testing library for Polkadot, Ethereum, and Solana wallet interactions using Playwright.
4
4
 
5
5
  ## Documentation
6
6
 
@@ -54,10 +54,10 @@ test('connect wallet and sign transaction', async ({ page, wallets }) => {
54
54
 
55
55
  await page.goto('http://localhost:3000')
56
56
  await page.click('button:has-text("Connect Wallet")')
57
- await metamask.authorize()
57
+ await metamask.approve()
58
58
 
59
59
  await page.click('button:has-text("Send Transaction")')
60
- await metamask.confirm()
60
+ await metamask.approve()
61
61
 
62
62
  await expect(page.locator('.transaction-success')).toBeVisible()
63
63
  })
@@ -80,10 +80,110 @@ test('multi-wallet test', async ({ page, wallets }) => {
80
80
  await talisman.importEthPrivateKey({ privateKey: '0x...', name: 'Bob' })
81
81
 
82
82
  await page.goto('http://localhost:3000')
83
- await metamask.authorize()
83
+ await metamask.approve()
84
84
  })
85
85
  ```
86
86
 
87
+ ### Setup Project Pattern
88
+
89
+ By default the browser context uses a temporary profile, so wallet state (imported accounts, passwords) is lost between runs. To import a seed phrase **once** and reuse the prepared state across all your specs, combine the `userDataDir` and `cloneUserDataDirFrom` options with [Playwright's setup project pattern](https://playwright.dev/docs/test-global-setup-teardown).
90
+
91
+ A setup project writes the prepared profile to a shared dir; spec projects then point a `userDataDir` at it (or clone it per worker for parallelism).
92
+
93
+ #### 1. Setup project — seed once
94
+
95
+ The setup test guards onboarding with a sentinel file so re-running `playwright test` doesn't try to onboard an already-prepared profile (the second run would deadlock on a UI that no longer matches the import flow). Delete `.cache/wallet-setup` to force a fresh seed.
96
+
97
+ ```typescript
98
+ // metamask.setup.ts
99
+ import fs from 'node:fs'
100
+ import path from 'node:path'
101
+ import { createWalletTest } from '@avalix/chroma'
102
+
103
+ const SETUP_DIR = '.cache/wallet-setup'
104
+ const SENTINEL = path.join(SETUP_DIR, '.chroma-onboarded')
105
+
106
+ const setup = createWalletTest({
107
+ wallets: [{ type: 'metamask' }],
108
+ userDataDir: SETUP_DIR,
109
+ })
110
+
111
+ setup('seed metamask', async ({ wallets }) => {
112
+ if (fs.existsSync(SENTINEL))
113
+ return
114
+
115
+ await wallets.metamask.importSeedPhrase({
116
+ seedPhrase: 'test test test test test test test test test test test junk',
117
+ })
118
+ fs.writeFileSync(SENTINEL, '')
119
+ })
120
+ ```
121
+
122
+ #### 2. Shared spec fixtures
123
+
124
+ Spec files import a shared `test` factory pointed at the prepared profile. Because MetaMask boots into a locked state on a previously-onboarded profile, each spec must call `wallets.metamask.unlock()` once (it's idempotent — when MetaMask is already unlocked the call is a no-op). On unlock, the MetaMask side panel is left open for the rest of the test session.
125
+
126
+ ```typescript
127
+ // fixtures.ts — shared by your spec files
128
+ import { createWalletTest } from '@avalix/chroma'
129
+
130
+ export const test = createWalletTest({
131
+ wallets: [{ type: 'metamask' }],
132
+ userDataDir: '.cache/wallet-setup',
133
+ })
134
+
135
+ export { expect } from '@playwright/test'
136
+ ```
137
+
138
+ ```typescript
139
+ // some.spec.ts
140
+ import { test } from './fixtures'
141
+
142
+ test('connect and sign', async ({ page, wallets }) => {
143
+ const metamask = wallets.metamask
144
+
145
+ await page.goto('http://localhost:3000')
146
+ await metamask.unlock()
147
+
148
+ await page.click('button:has-text("Connect Wallet")')
149
+ await metamask.approve()
150
+
151
+ await page.click('button:has-text("Sign Message")')
152
+ await metamask.approve()
153
+ })
154
+ ```
155
+
156
+ #### 3. Wire up the projects
157
+
158
+ ```typescript
159
+ // playwright.config.ts
160
+ export default defineConfig({
161
+ workers: 1,
162
+ projects: [
163
+ { name: 'setup', testMatch: /.*\.setup\.ts/ },
164
+ {
165
+ name: 'metamask',
166
+ testMatch: /.*\.spec\.ts/,
167
+ dependencies: ['setup'],
168
+ },
169
+ ],
170
+ })
171
+ ```
172
+
173
+ #### Parallel workers (advanced)
174
+
175
+ Chrome locks `userDataDir`, so multiple workers can't share the same path concurrently. Use `cloneUserDataDirFrom` plus a per-worker `userDataDir` to give each worker its own copy of the prepared profile:
176
+
177
+ ```typescript
178
+ export const test = createWalletTest({
179
+ wallets: [{ type: 'metamask' }],
180
+ userDataDir: ({ workerIndex }) => `.cache/wallet-w${workerIndex}`,
181
+ cloneUserDataDirFrom: '.cache/wallet-setup',
182
+ })
183
+ ```
184
+
185
+ Set `workers: undefined` (or higher) once you've validated parallel runs in your project — interaction between cloned profiles, MetaMask's locked-state recovery, and Playwright's side-panel detection is still being hardened.
186
+
87
187
  ## Features
88
188
 
89
189
  - **Easy Extension Setup** - Download wallet extensions with a single command
package/dist/index.d.mts CHANGED
@@ -18,6 +18,7 @@ type JUnitReporterOptions = {
18
18
  outputFile?: string;
19
19
  stripANSIControlSequences?: boolean;
20
20
  includeProjectInTestName?: boolean;
21
+ includeRetries?: boolean;
21
22
  };
22
23
  type JsonReporterOptions = {
23
24
  outputFile?: string;
@@ -31,6 +32,7 @@ type HtmlReporterOptions = {
31
32
  title?: string;
32
33
  noSnippets?: boolean;
33
34
  noCopyPrompt?: boolean;
35
+ doNotInlineAssets?: boolean;
34
36
  };
35
37
  type ReporterDescription = Readonly<['blob'] | ['blob', BlobReporterOptions] | ['dot'] | ['line'] | ['list'] | ['list', ListReporterOptions] | ['github'] | ['junit'] | ['junit', JUnitReporterOptions] | ['json'] | ['json', JsonReporterOptions] | ['html'] | ['html', HtmlReporterOptions] | ['null'] | [string] | [string, any]>;
36
38
  type UseOptions<TestArgs, WorkerArgs> = Partial<WorkerArgs> & Partial<TestArgs>;
@@ -226,6 +228,12 @@ interface TestProject<TestArgs = {}, WorkerArgs = {}> {
226
228
  * for details.
227
229
  */
228
230
  pathTemplate?: string;
231
+ /**
232
+ * Controls how children of the snapshot root are matched against the actual accessibility tree. This is equivalent to
233
+ * adding a `/children` property at the top of every aria snapshot template. Individual snapshots can override this by
234
+ * including an explicit `/children` property.
235
+ */
236
+ children?: "contain" | "equal" | "deep-equal";
229
237
  };
230
238
  /**
231
239
  * Configuration for the
@@ -711,6 +719,10 @@ interface FullProject<TestArgs = {}, WorkerArgs = {}> {
711
719
  * See [testProject.grepInvert](https://playwright.dev/docs/api/class-testproject#test-project-grep-invert).
712
720
  */
713
721
  grepInvert: null | RegExp | Array<RegExp>;
722
+ /**
723
+ * See [testProject.ignoreSnapshots](https://playwright.dev/docs/api/class-testproject#test-project-ignore-snapshots).
724
+ */
725
+ ignoreSnapshots: boolean;
714
726
  /**
715
727
  * See [testProject.metadata](https://playwright.dev/docs/api/class-testproject#test-project-metadata).
716
728
  */
@@ -1101,6 +1113,12 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
1101
1113
  * for details.
1102
1114
  */
1103
1115
  pathTemplate?: string;
1116
+ /**
1117
+ * Controls how children of the snapshot root are matched against the actual accessibility tree. This is equivalent to
1118
+ * adding a `/children` property at the top of every aria snapshot template. Individual snapshots can override this by
1119
+ * including an explicit `/children` property.
1120
+ */
1121
+ children?: "contain" | "equal" | "deep-equal";
1104
1122
  };
1105
1123
  /**
1106
1124
  * Configuration for the
@@ -6708,6 +6726,7 @@ interface PlaywrightWorkerOptions {
6708
6726
  * - `'retain-on-failure'`: Record trace for each test. When test run passes, remove the recorded trace.
6709
6727
  * - `'retain-on-first-failure'`: Record trace for the first run of each test, but not for retries. When test run
6710
6728
  * passes, remove the recorded trace.
6729
+ * - `'retain-on-failure-and-retries'`: Record trace for each test run. Retains all traces when an attempt fails.
6711
6730
  *
6712
6731
  * For more control, pass an object that specifies `mode` and trace features to enable.
6713
6732
  *
@@ -6745,6 +6764,10 @@ interface PlaywrightWorkerOptions {
6745
6764
  * down to fit into 800x800. If `viewport` is not configured explicitly the video size defaults to 800x450. Actual
6746
6765
  * picture of each page will be scaled down if necessary to fit the specified size.
6747
6766
  *
6767
+ * To annotate actions in the video, pass `show` with `action` and/or `test` sub-options. The `action` option controls
6768
+ * visual highlights on interacted elements with an optional `delay` in milliseconds (defaults to `500`). The `test`
6769
+ * option controls which test information is displayed as a status overlay.
6770
+ *
6748
6771
  * **Usage**
6749
6772
  *
6750
6773
  * ```js
@@ -6763,10 +6786,22 @@ interface PlaywrightWorkerOptions {
6763
6786
  video: VideoMode | /** deprecated */'retry-with-video' | {
6764
6787
  mode: VideoMode;
6765
6788
  size?: ViewportSize;
6789
+ show?: {
6790
+ actions?: {
6791
+ duration?: number;
6792
+ position?: 'top-left' | 'top' | 'top-right' | 'bottom-left' | 'bottom' | 'bottom-right';
6793
+ fontSize?: number;
6794
+ };
6795
+ test?: {
6796
+ level?: 'file' | 'title' | 'step';
6797
+ position?: 'top-left' | 'top' | 'top-right' | 'bottom-left' | 'bottom' | 'bottom-right';
6798
+ fontSize?: number;
6799
+ };
6800
+ };
6766
6801
  };
6767
6802
  }
6768
6803
  type ScreenshotMode = 'off' | 'on' | 'only-on-failure' | 'on-first-failure';
6769
- type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure';
6804
+ type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure' | 'retain-on-failure-and-retries';
6770
6805
  type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';
6771
6806
  /**
6772
6807
  * Playwright Test provides many options to configure test environment,
@@ -7518,6 +7553,11 @@ type ExcludeProps<A, B> = { [K in Exclude<keyof A, keyof B>]: A[K] };
7518
7553
  type CustomProperties<T> = ExcludeProps<T, PlaywrightTestOptions & PlaywrightWorkerOptions & PlaywrightTestArgs & PlaywrightWorkerArgs>;
7519
7554
  type PlaywrightTestProject<TestArgs = {}, WorkerArgs = {}> = Project<PlaywrightTestOptions & CustomProperties<TestArgs>, PlaywrightWorkerOptions & CustomProperties<WorkerArgs>>;
7520
7555
  type PlaywrightTestConfig<TestArgs = {}, WorkerArgs = {}> = Config<PlaywrightTestOptions & CustomProperties<TestArgs>, PlaywrightWorkerOptions & CustomProperties<WorkerArgs>>;
7556
+ // Use the global URLPattern type if available (Node.js 22+, modern browsers),
7557
+ // otherwise fall back to `never` so it disappears from union types.
7558
+ type URLPattern = typeof globalThis extends {
7559
+ URLPattern: infer T;
7560
+ } ? T : never;
7521
7561
  type AsymmetricMatcher = Record<string, any>;
7522
7562
  interface AsymmetricMatchers {
7523
7563
  /**
@@ -7703,7 +7743,11 @@ interface AsymmetricMatchers {
7703
7743
  */
7704
7744
  interface GenericAssertions<R> {
7705
7745
  /**
7706
- * Makes the assertion check for the opposite condition. For example, the following code passes:
7746
+ * Makes the assertion check for the opposite condition.
7747
+ *
7748
+ * **Usage**
7749
+ *
7750
+ * For example, the following code passes:
7707
7751
  *
7708
7752
  * ```js
7709
7753
  * const value = 1;
@@ -7712,6 +7756,34 @@ interface GenericAssertions<R> {
7712
7756
  *
7713
7757
  */
7714
7758
  not: GenericAssertions<R>;
7759
+ /**
7760
+ * Use `resolves` to unwrap the value of a fulfilled promise so any other matcher can be chained. If the promise is
7761
+ * rejected the assertion fails.
7762
+ *
7763
+ * For example, this code tests that the promise resolves and that the resulting value is `'lemon'`:
7764
+ *
7765
+ * ```js
7766
+ * test('resolves to lemon', async () => {
7767
+ * await expect(Promise.resolve('lemon')).resolves.toBe('lemon');
7768
+ * });
7769
+ * ```
7770
+ *
7771
+ */
7772
+ resolves: GenericAssertions<R>;
7773
+ /**
7774
+ * Use `.rejects` to unwrap the reason of a rejected promise so any other matcher can be chained. If the promise is
7775
+ * fulfilled the assertion fails.
7776
+ *
7777
+ * For example, this code tests that the promise rejects with reason `'octopus'`:
7778
+ *
7779
+ * ```js
7780
+ * test('rejects to octopus', async () => {
7781
+ * await expect(Promise.reject(new Error('octopus'))).rejects.toThrow('octopus');
7782
+ * });
7783
+ * ```
7784
+ *
7785
+ */
7786
+ rejects: GenericAssertions<R>;
7715
7787
  /**
7716
7788
  * Compares value with
7717
7789
  * [`expected`](https://playwright.dev/docs/api/class-genericassertions#generic-assertions-to-be-option-expected) by
@@ -8339,8 +8411,11 @@ interface APIResponseAssertions {
8339
8411
  */
8340
8412
  toBeOK(): Promise<void>;
8341
8413
  /**
8342
- * Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
8343
- * successful:
8414
+ * Makes the assertion check for the opposite condition.
8415
+ *
8416
+ * **Usage**
8417
+ *
8418
+ * For example, this code tests that the response status is not successful:
8344
8419
  *
8345
8420
  * ```js
8346
8421
  * await expect(response).not.toBeOK();
@@ -9304,8 +9379,11 @@ interface LocatorAssertions {
9304
9379
  timeout?: number;
9305
9380
  }): Promise<void>;
9306
9381
  /**
9307
- * Makes the assertion check for the opposite condition. For example, this code tests that the Locator doesn't contain
9308
- * text `"error"`:
9382
+ * Makes the assertion check for the opposite condition.
9383
+ *
9384
+ * **Usage**
9385
+ *
9386
+ * For example, this code tests that the Locator doesn't contain text `"error"`:
9309
9387
  *
9310
9388
  * ```js
9311
9389
  * await expect(locator).not.toContainText('error');
@@ -9389,6 +9467,9 @@ interface PageAssertions {
9389
9467
  * // Check for the page URL to contain 'doc', followed by an optional 's', followed by '/'
9390
9468
  * await expect(page).toHaveURL(/docs?\//);
9391
9469
  *
9470
+ * // Check for the page URL to match the URL pattern
9471
+ * await expect(page).toHaveURL(new URLPattern({ pathname: '/docs/*' }));
9472
+ *
9392
9473
  * // Check for the predicate to be satisfied
9393
9474
  * // For example: verify query strings
9394
9475
  * await expect(page).toHaveURL(url => {
@@ -9404,7 +9485,7 @@ interface PageAssertions {
9404
9485
  * against the current browser URL.
9405
9486
  * @param options
9406
9487
  */
9407
- toHaveURL(url: string | RegExp | ((url: URL) => boolean), options?: {
9488
+ toHaveURL(url: string | RegExp | URLPattern | ((url: URL) => boolean), options?: {
9408
9489
  /**
9409
9490
  * Whether to perform case-insensitive match.
9410
9491
  * [`ignoreCase`](https://playwright.dev/docs/api/class-pageassertions#page-assertions-to-have-url-option-ignore-case)
@@ -9418,8 +9499,11 @@ interface PageAssertions {
9418
9499
  timeout?: number;
9419
9500
  }): Promise<void>;
9420
9501
  /**
9421
- * Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't
9422
- * contain `"error"`:
9502
+ * Makes the assertion check for the opposite condition.
9503
+ *
9504
+ * **Usage**
9505
+ *
9506
+ * For example, this code tests that the page URL doesn't contain `"error"`:
9423
9507
  *
9424
9508
  * ```js
9425
9509
  * await expect(page).not.toHaveURL('error');
@@ -9956,9 +10040,8 @@ declare function createMetaMaskWallet(extensionId: string, context: BrowserConte
9956
10040
  seedPhrase: string;
9957
10041
  }) => Promise<void>;
9958
10042
  unlock: () => Promise<void>;
9959
- authorize: () => Promise<void>;
10043
+ approve: () => Promise<void>;
9960
10044
  reject: () => Promise<void>;
9961
- confirm: () => Promise<void>;
9962
10045
  };
9963
10046
  type PolkadotJsWalletInstance = ReturnType<typeof createPolkadotJsWallet>;
9964
10047
  type TalismanWalletInstance = ReturnType<typeof createTalismanWallet>;
@@ -9973,31 +10056,43 @@ interface WalletAccount {
9973
10056
  }
9974
10057
  interface WalletConfig {
9975
10058
  type: WalletType;
9976
- downloadUrl?: string;
9977
10059
  }
9978
10060
  interface WalletTypeMap {
9979
10061
  'polkadot-js': PolkadotJsWalletInstance;
9980
10062
  'talisman': TalismanWalletInstance;
9981
10063
  'metamask': MetaMaskWalletInstance;
9982
10064
  }
9983
- type Wallets = WalletTypeMap;
9984
10065
  type ConfiguredWallets<T extends readonly WalletConfig[]> = { [K in T[number]['type']]: WalletTypeMap[K] };
9985
- type ExtendedPage = Page & {
9986
- __extensionContext: BrowserContext;
9987
- __walletExtensionIds: Map<string, string>;
9988
- };
9989
10066
  interface ChromaTestOptions<T extends readonly WalletConfig[] = WalletConfig[]> {
9990
10067
  wallets?: T;
9991
10068
  headless?: boolean;
9992
10069
  slowMo?: number;
10070
+ /**
10071
+ * Persistent profile dir for the browser context.
10072
+ * - Empty/undefined (default): temp dir is used; state is lost each run.
10073
+ * - String: shared profile path. Requires `workers: 1` if used by multiple workers.
10074
+ * - Function: receives the worker index, returns the path. Use for parallel
10075
+ * isolation (e.g. `({ workerIndex }) => `.cache/wallet-w${workerIndex}``).
10076
+ */
10077
+ userDataDir?: string | ((info: {
10078
+ workerIndex: number;
10079
+ }) => string | Promise<string>);
10080
+ /**
10081
+ * If set, the source dir is copied into `userDataDir` before launch (target is
10082
+ * removed first). Use with the Playwright setup-project pattern: a setup
10083
+ * project writes to the source dir, then test projects clone it per worker so
10084
+ * each parallel worker boots from the same prepared state.
10085
+ * No-op if `userDataDir` resolves to an empty string.
10086
+ */
10087
+ cloneUserDataDirFrom?: string;
9993
10088
  }
9994
- interface WalletFixtures<W = Wallets> {
9995
- page: ExtendedPage;
10089
+ interface WalletFixtures<W = WalletTypeMap> {
10090
+ page: Page;
9996
10091
  wallets: W;
9997
10092
  }
9998
10093
  interface WalletWorkerFixtures {
9999
10094
  walletContext: BrowserContext;
10000
- walletExtensionIds: Map<string, string>;
10095
+ walletExtensionIds: Map<WalletType, string>;
10001
10096
  }
10002
10097
  //#endregion
10003
10098
  //#region src/context-playwright/index.d.ts