proxymail-qa 0.7.0 → 0.8.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 +18 -0
- package/dist/index.d.ts +18 -1
- package/dist/index.js +30 -1
- package/dist/playwright-reporter.d.ts +11 -0
- package/dist/playwright-reporter.js +82 -0
- package/dist/playwright.d.ts +5 -0
- package/dist/playwright.js +25 -0
- package/dist/test-name.d.ts +1 -0
- package/dist/test-name.js +3 -0
- package/package.json +18 -1
package/README.md
CHANGED
|
@@ -52,6 +52,24 @@ test('sign-up sends a working code', async ({ page, inbox }) => {
|
|
|
52
52
|
});
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
In a run (the GitHub Action, or `startRun` in `globalSetup`), the report can say
|
|
56
|
+
which test each email came from. Use the `qa` fixture from
|
|
57
|
+
`proxymail-qa/playwright`, a client that names its test in every call, and add
|
|
58
|
+
the reporter, which records failed and flaky tests and the totals:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// playwright.config.ts
|
|
62
|
+
reporter: [['list'], ['proxymail-qa/playwright/reporter']],
|
|
63
|
+
|
|
64
|
+
// signup.spec.ts
|
|
65
|
+
import { test, expect } from 'proxymail-qa/playwright';
|
|
66
|
+
|
|
67
|
+
test('sign-up sends one code', async ({ page, qa }) => {
|
|
68
|
+
const { address } = await qa.createAddress();
|
|
69
|
+
// …
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
55
73
|
## Cypress
|
|
56
74
|
|
|
57
75
|
Register tasks in `cypress.config.ts`, since Cypress runs them in Node:
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,19 @@ export interface ClientOptions {
|
|
|
3
3
|
baseUrl?: string;
|
|
4
4
|
fetch?: typeof fetch;
|
|
5
5
|
run?: string | null;
|
|
6
|
+
test?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface TestResultInput {
|
|
9
|
+
name: string;
|
|
10
|
+
status: 'passed' | 'failed' | 'flaky' | 'skipped';
|
|
11
|
+
durationMs?: number;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface TestTotals {
|
|
15
|
+
passed: number;
|
|
16
|
+
failed: number;
|
|
17
|
+
skipped?: number;
|
|
18
|
+
flaky?: number;
|
|
6
19
|
}
|
|
7
20
|
export interface Persona {
|
|
8
21
|
first_name: string | null;
|
|
@@ -190,7 +203,7 @@ export interface CountOptions {
|
|
|
190
203
|
subtype?: CountSubtype | CountSubtype[];
|
|
191
204
|
}
|
|
192
205
|
export type CountSubtype = 'otp' | 'coupon' | 'verify' | 'magic_signin' | 'invite' | 'reset' | 'other' | 'any';
|
|
193
|
-
export type CheckId = 'spf' | 'dkim' | 'dmarc' | 'local_links' | 'plain_http' | 'expected_host' | 'expiry' | 'plain_text' | 'size' | 'images_only' | 'expected_text' | 'language';
|
|
206
|
+
export type CheckId = 'spf' | 'dkim' | 'dmarc' | 'local_links' | 'plain_http' | 'expected_host' | 'expiry' | 'plain_text' | 'size' | 'images_only' | 'layout' | 'expected_text' | 'language';
|
|
194
207
|
export interface EmailCheck {
|
|
195
208
|
check: CheckId;
|
|
196
209
|
status: 'pass' | 'fail' | 'warn' | 'skip';
|
|
@@ -266,7 +279,9 @@ export declare class ProxyMailQA {
|
|
|
266
279
|
private readonly baseUrl;
|
|
267
280
|
private readonly fetcher;
|
|
268
281
|
run: string | null;
|
|
282
|
+
readonly test: string | null;
|
|
269
283
|
constructor(options?: ClientOptions);
|
|
284
|
+
forTest(name: string): ProxyMailQA;
|
|
270
285
|
private request;
|
|
271
286
|
project(): Promise<Record<string, unknown>>;
|
|
272
287
|
createAddress(input: CreateAddressInput & {
|
|
@@ -329,6 +344,8 @@ export declare class ProxyMailQA {
|
|
|
329
344
|
getRun(id?: string): Promise<Run>;
|
|
330
345
|
runReport(id?: string): Promise<string>;
|
|
331
346
|
listRuns(limit?: number): Promise<Run[]>;
|
|
347
|
+
recordTest(result: TestResultInput, run?: string): Promise<void>;
|
|
348
|
+
recordTestTotals(totals: TestTotals, run?: string): Promise<void>;
|
|
332
349
|
addRunNote(text: string, options?: {
|
|
333
350
|
failed?: boolean;
|
|
334
351
|
address?: string;
|
package/dist/index.js
CHANGED
|
@@ -42,15 +42,25 @@ export class ProxyMailQA {
|
|
|
42
42
|
fetcher;
|
|
43
43
|
// The run this client's calls add to, if any.
|
|
44
44
|
run;
|
|
45
|
+
// The test they belong to, if any.
|
|
46
|
+
test;
|
|
45
47
|
constructor(options = {}) {
|
|
46
48
|
const env = globalThis.process?.env;
|
|
47
49
|
const apiKey = options.apiKey ?? env?.PROXYMAIL_QA_API_KEY;
|
|
48
50
|
if (!apiKey)
|
|
49
51
|
throw new Error('Pass apiKey or set PROXYMAIL_QA_API_KEY. Create a key in Project settings → API keys.');
|
|
50
52
|
this.apiKey = apiKey;
|
|
51
|
-
|
|
53
|
+
// PROXYMAIL_QA_API_URL, as the Python helper reads it, may end in /v1.
|
|
54
|
+
this.baseUrl = (options.baseUrl ?? (env?.PROXYMAIL_QA_API_URL?.trim().replace(/\/v1\/?$/, '') || 'https://api.proxymail.qa')).replace(/\/+$/, '');
|
|
52
55
|
this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
53
56
|
this.run = options.run === undefined ? env?.PROXYMAIL_QA_RUN?.trim() || null : options.run;
|
|
57
|
+
this.test = options.test?.trim() || null;
|
|
58
|
+
}
|
|
59
|
+
// A client for one test: the same key and run, and every call says which
|
|
60
|
+
// test it belongs to, so the run's report shows which test each email came
|
|
61
|
+
// from. proxymail-qa/playwright's qa fixture does this for you.
|
|
62
|
+
forTest(name) {
|
|
63
|
+
return new ProxyMailQA({ apiKey: this.apiKey, baseUrl: this.baseUrl, fetch: this.fetcher, run: this.run, test: name });
|
|
54
64
|
}
|
|
55
65
|
// retry: this call is asked again if nothing arrives, so the run only
|
|
56
66
|
// records the last try's "nothing arrived".
|
|
@@ -61,6 +71,8 @@ export class ProxyMailQA {
|
|
|
61
71
|
Authorization: `Bearer ${this.apiKey}`,
|
|
62
72
|
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
|
|
63
73
|
...(this.run ? { 'Proxy-Mail-QA-Run': this.run, ...(retry ? { 'Proxy-Mail-QA-Run-Retry': 'true' } : {}) } : {}),
|
|
74
|
+
// Percent-encoded: a test's name can hold characters headers can't.
|
|
75
|
+
...(this.run && this.test ? { 'Proxy-Mail-QA-Test': encodeURIComponent(this.test) } : {}),
|
|
64
76
|
},
|
|
65
77
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
66
78
|
});
|
|
@@ -421,6 +433,23 @@ export class ProxyMailQA {
|
|
|
421
433
|
}
|
|
422
434
|
// A line of your own in the run, such as a test that failed for a reason
|
|
423
435
|
// of its own. failed: true fails the run.
|
|
436
|
+
// A test's result in the run (a failed one fails it), from a test runner's
|
|
437
|
+
// reporter: proxymail-qa/playwright/reporter sends failed and flaky tests,
|
|
438
|
+
// then the totals.
|
|
439
|
+
recordTest(result, run) {
|
|
440
|
+
const id = run ?? this.requireRun();
|
|
441
|
+
return this.request('POST', `/runs/${encodeURIComponent(id)}/tests`, {
|
|
442
|
+
name: result.name,
|
|
443
|
+
status: result.status,
|
|
444
|
+
duration_ms: result.durationMs,
|
|
445
|
+
error: result.error,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
// How the suite went, for the top of the run's report.
|
|
449
|
+
recordTestTotals(totals, run) {
|
|
450
|
+
const id = run ?? this.requireRun();
|
|
451
|
+
return this.request('POST', `/runs/${encodeURIComponent(id)}/tests`, { totals });
|
|
452
|
+
}
|
|
424
453
|
addRunNote(text, options = {}) {
|
|
425
454
|
const id = options.run ?? this.requireRun();
|
|
426
455
|
return this.request('POST', `/runs/${encodeURIComponent(id)}/notes`, { text, failed: options.failed ?? false, address: options.address });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FullResult, Reporter, Suite } from '@playwright/test/reporter';
|
|
2
|
+
import { type ClientOptions } from './index.js';
|
|
3
|
+
export default class ProxyMailQAReporter implements Reporter {
|
|
4
|
+
private readonly options;
|
|
5
|
+
private suite;
|
|
6
|
+
constructor(options?: Pick<ClientOptions, 'apiKey' | 'baseUrl'>);
|
|
7
|
+
onBegin(_config: unknown, suite: Suite): void;
|
|
8
|
+
onEnd(_result: FullResult): Promise<void>;
|
|
9
|
+
printsToStdio(): boolean;
|
|
10
|
+
private client;
|
|
11
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ProxyMailQA } from './index.js';
|
|
2
|
+
import { testName } from './test-name.js';
|
|
3
|
+
// Enough for a report; the rest are in Playwright's own.
|
|
4
|
+
const MAX_RESULTS = 200;
|
|
5
|
+
export default class ProxyMailQAReporter {
|
|
6
|
+
options;
|
|
7
|
+
suite = null;
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = options;
|
|
10
|
+
}
|
|
11
|
+
onBegin(_config, suite) {
|
|
12
|
+
this.suite = suite;
|
|
13
|
+
}
|
|
14
|
+
async onEnd(_result) {
|
|
15
|
+
const qa = this.client();
|
|
16
|
+
if (!qa || !this.suite)
|
|
17
|
+
return;
|
|
18
|
+
const totals = { passed: 0, failed: 0, skipped: 0, flaky: 0 };
|
|
19
|
+
const results = [];
|
|
20
|
+
for (const test of this.suite.allTests()) {
|
|
21
|
+
const outcome = test.outcome();
|
|
22
|
+
if (outcome === 'expected')
|
|
23
|
+
totals.passed++;
|
|
24
|
+
else if (outcome === 'skipped')
|
|
25
|
+
totals.skipped++;
|
|
26
|
+
else {
|
|
27
|
+
const flaky = outcome === 'flaky';
|
|
28
|
+
if (flaky)
|
|
29
|
+
totals.flaky++;
|
|
30
|
+
else
|
|
31
|
+
totals.failed++;
|
|
32
|
+
const last = test.results.at(-1);
|
|
33
|
+
results.push({
|
|
34
|
+
name: nameOf(test),
|
|
35
|
+
status: flaky ? 'flaky' : 'failed',
|
|
36
|
+
durationMs: last?.duration,
|
|
37
|
+
error: flaky ? undefined : withStep(failedStep(last?.steps ?? []), last?.error?.message),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Failures never fail the test run itself: the report is evidence.
|
|
42
|
+
for (const result of results.slice(0, MAX_RESULTS))
|
|
43
|
+
await qa.recordTest(result).catch(() => { });
|
|
44
|
+
await qa.recordTestTotals(totals).catch(() => { });
|
|
45
|
+
}
|
|
46
|
+
printsToStdio() {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
client() {
|
|
50
|
+
try {
|
|
51
|
+
const qa = new ProxyMailQA(this.options);
|
|
52
|
+
return qa.run ? qa : null;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// No API key: nothing to report to.
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// titlePath: the root, the project, the file, then describe blocks and the title.
|
|
61
|
+
function nameOf(test) {
|
|
62
|
+
const [, project = '', , ...path] = test.titlePath();
|
|
63
|
+
return testName(project, path);
|
|
64
|
+
}
|
|
65
|
+
// The innermost step that failed: a test.step of the test's own, or the
|
|
66
|
+
// browser action or assertion inside it, such as a click or an expect.
|
|
67
|
+
function failedStep(steps) {
|
|
68
|
+
for (const step of steps) {
|
|
69
|
+
if (!step.error)
|
|
70
|
+
continue;
|
|
71
|
+
return failedStep(step.steps) ?? step;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
// "At “Fill in the code” › locator.fill(…): Error: …", so the report says
|
|
76
|
+
// which browser step failed as well as why.
|
|
77
|
+
function withStep(step, message) {
|
|
78
|
+
if (!step)
|
|
79
|
+
return message;
|
|
80
|
+
const path = step.titlePath().filter(Boolean).slice(-2).join(' › ');
|
|
81
|
+
return `At “${path}”: ${message ?? 'failed'}`;
|
|
82
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ProxyMailQA } from './index.js';
|
|
2
|
+
export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions & {
|
|
3
|
+
qa: ProxyMailQA;
|
|
4
|
+
}, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
|
|
5
|
+
export { expect } from '@playwright/test';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Playwright tests with Proxy Mail QA. The qa fixture is a client for the
|
|
2
|
+
// test it's in: in a run (PROXYMAIL_QA_RUN, set by the GitHub Action or by
|
|
3
|
+
// startRun in globalSetup), each call says which test it belongs to, so the
|
|
4
|
+
// run's report shows which test each email came from.
|
|
5
|
+
//
|
|
6
|
+
// import { test, expect } from 'proxymail-qa/playwright';
|
|
7
|
+
//
|
|
8
|
+
// test('sign-up sends one code', async ({ page, qa }) => {
|
|
9
|
+
// const { address } = await qa.createAddress();
|
|
10
|
+
// …
|
|
11
|
+
// });
|
|
12
|
+
//
|
|
13
|
+
// Add proxymail-qa/playwright/reporter to the config's reporters to record
|
|
14
|
+
// failed tests and the totals in the run too.
|
|
15
|
+
import { test as base } from '@playwright/test';
|
|
16
|
+
import { ProxyMailQA } from './index.js';
|
|
17
|
+
import { testName } from './test-name.js';
|
|
18
|
+
export const test = base.extend({
|
|
19
|
+
// eslint-disable-next-line no-empty-pattern
|
|
20
|
+
qa: async ({}, use, testInfo) => {
|
|
21
|
+
// titlePath starts with the file.
|
|
22
|
+
await use(new ProxyMailQA().forTest(testName(testInfo.project.name, testInfo.titlePath.slice(1))));
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
export { expect } from '@playwright/test';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const testName: (project: string, path: readonly string[]) => string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proxymail-qa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Disposable test inboxes for Playwright, Cypress and scripts: create an address, then wait for its verification code or link.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Proxy Mail QA <hello@proxymail.qa> (https://proxymail.qa)",
|
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
".": {
|
|
30
30
|
"types": "./dist/index.d.ts",
|
|
31
31
|
"default": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./playwright": {
|
|
34
|
+
"types": "./dist/playwright.d.ts",
|
|
35
|
+
"default": "./dist/playwright.js"
|
|
36
|
+
},
|
|
37
|
+
"./playwright/reporter": {
|
|
38
|
+
"types": "./dist/playwright-reporter.d.ts",
|
|
39
|
+
"default": "./dist/playwright-reporter.js"
|
|
32
40
|
}
|
|
33
41
|
},
|
|
34
42
|
"files": [
|
|
@@ -46,7 +54,16 @@
|
|
|
46
54
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
47
55
|
"prepublishOnly": "npm run build && npm test"
|
|
48
56
|
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@playwright/test": ">=1.40.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@playwright/test": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
49
65
|
"devDependencies": {
|
|
66
|
+
"@playwright/test": "^1.64.1",
|
|
50
67
|
"typescript": "^5.6.0",
|
|
51
68
|
"vitest": "^5.0.3"
|
|
52
69
|
}
|