proxymail-qa 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +108 -2
- package/dist/index.d.ts +145 -0
- package/dist/index.js +153 -0
- package/package.json +45 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sidequest Explorers Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,109 @@
|
|
|
1
|
-
#
|
|
1
|
+
# proxymail-qa
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Disposable test inboxes for Playwright, Cypress and scripts. Create an address,
|
|
4
|
+
sign up with it in the app you're testing, and get the verification code or
|
|
5
|
+
link from the email in one call that waits for it.
|
|
6
|
+
|
|
7
|
+
Get an API key in [app.proxymail.qa](https://app.proxymail.qa) → Project
|
|
8
|
+
settings → API keys (the Free plan includes it), and set it as
|
|
9
|
+
`PROXYMAIL_QA_API_KEY`. Full guide: https://proxymail.qa/docs/api
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { ProxyMailQA } from 'proxymail-qa';
|
|
13
|
+
|
|
14
|
+
const qa = new ProxyMailQA(); // reads PROXYMAIL_QA_API_KEY
|
|
15
|
+
const { address, persona, password } = await qa.createAddress({ persona: true, password: true, label: 'Sign-up test' });
|
|
16
|
+
// ...sign up with the address, persona and password...
|
|
17
|
+
const { value: code } = await qa.waitForCode(address); // waits up to 30 s
|
|
18
|
+
const { value: link } = await qa.waitForLink(address, { type: 'reset', timeout: 60 });
|
|
19
|
+
await qa.deleteAddress(address);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Waits return the code or link on its own, with the sending service and its
|
|
23
|
+
expiry, never the email's text. They throw `ProxyMailQAError` (status 404) when
|
|
24
|
+
nothing arrives in time.
|
|
25
|
+
|
|
26
|
+
## Playwright
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { test as base, expect } from '@playwright/test';
|
|
30
|
+
import { ProxyMailQA } from 'proxymail-qa';
|
|
31
|
+
|
|
32
|
+
const test = base.extend<{ inbox: { address: string } }>({
|
|
33
|
+
inbox: async ({}, use) => {
|
|
34
|
+
const qa = new ProxyMailQA();
|
|
35
|
+
const created = await qa.createAddress({ persona: true });
|
|
36
|
+
await use(created);
|
|
37
|
+
await qa.deleteAddress(created.address);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('sign-up sends a working code', async ({ page, inbox }) => {
|
|
42
|
+
const submitted = new Date();
|
|
43
|
+
await page.goto('/signup');
|
|
44
|
+
await page.getByLabel('Email').fill(inbox.address);
|
|
45
|
+
await page.getByRole('button', { name: 'Sign up' }).click();
|
|
46
|
+
const { value } = await new ProxyMailQA().waitForCode(inbox.address, { since: submitted });
|
|
47
|
+
await page.getByLabel('Code').fill(value);
|
|
48
|
+
await expect(page).toHaveURL(/welcome/);
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Cypress
|
|
53
|
+
|
|
54
|
+
Register tasks in `cypress.config.ts`, since Cypress runs them in Node:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { ProxyMailQA } from 'proxymail-qa';
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
e2e: {
|
|
61
|
+
setupNodeEvents(on) {
|
|
62
|
+
const qa = new ProxyMailQA();
|
|
63
|
+
on('task', {
|
|
64
|
+
createAddress: () => qa.createAddress({ persona: true }),
|
|
65
|
+
waitForLink: (address: string) => qa.waitForLink(address, { type: 'verify' }),
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
cy.task('createAddress').then(({ address }) => {
|
|
74
|
+
cy.get('[name=email]').type(address);
|
|
75
|
+
cy.contains('Sign up').click();
|
|
76
|
+
cy.task('waitForLink', address, { timeout: 35000 }).then(({ value }) => cy.visit(value));
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## More
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// A persona with a date of birth for an age band, and a country.
|
|
84
|
+
await qa.createAddress({ persona: true, age: 'under13', country: 'GB' });
|
|
85
|
+
|
|
86
|
+
// The link that opens a mobile app (myapp://…) when the email also has a web one.
|
|
87
|
+
const { value: appLink } = await qa.waitForLink(address, { app: true });
|
|
88
|
+
|
|
89
|
+
// The email itself, for template tests (the HTML is cleaned when it arrives).
|
|
90
|
+
const [latest] = await qa.messages(address, { includeHtml: true, limit: 1 });
|
|
91
|
+
|
|
92
|
+
// Unusual but valid versions of the address, and invalid ones to reject.
|
|
93
|
+
const { delivered, invalid } = await qa.variants(address);
|
|
94
|
+
|
|
95
|
+
// After the app changes the test account's password, keep the saved one in step.
|
|
96
|
+
await qa.setPassword(address, { password: 'the-new-test-password' });
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
When a wait comes back empty, the error says why as far as Proxy Mail QA can
|
|
100
|
+
tell, for example that the app's email provider only sends to verified
|
|
101
|
+
addresses (sandbox mode): read `error.hint`, or call `qa.checkDelivery(address)`
|
|
102
|
+
at any time. Retrying won't help until the app can email any address.
|
|
103
|
+
|
|
104
|
+
Mobile apps (XCUITest, Espresso, Maestro, Flutter, Detox and Appium):
|
|
105
|
+
https://proxymail.qa/docs/mobile
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export interface ClientOptions {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
fetch?: typeof fetch;
|
|
5
|
+
}
|
|
6
|
+
export interface Persona {
|
|
7
|
+
first_name: string | null;
|
|
8
|
+
last_name: string | null;
|
|
9
|
+
username: string | null;
|
|
10
|
+
date_of_birth?: string;
|
|
11
|
+
country?: string;
|
|
12
|
+
}
|
|
13
|
+
export type AgeBand = 'adult' | 'over21' | 'under18' | 'under15' | 'under13' | 'any' | 'turns18tomorrow' | 'turned18today';
|
|
14
|
+
export interface Address {
|
|
15
|
+
address: string;
|
|
16
|
+
label: string | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
expires_at: string;
|
|
19
|
+
persona: Persona | null;
|
|
20
|
+
notes: string | null;
|
|
21
|
+
tags: string[];
|
|
22
|
+
has_password: boolean;
|
|
23
|
+
email_count: number;
|
|
24
|
+
latest: Omit<Match, 'address' | 'email_id' | 'service_domain'> | null;
|
|
25
|
+
}
|
|
26
|
+
export interface CreatedAddress extends Address {
|
|
27
|
+
password?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface Match {
|
|
30
|
+
address: string;
|
|
31
|
+
type: 'code' | 'link';
|
|
32
|
+
subtype: string;
|
|
33
|
+
value: string;
|
|
34
|
+
service: string | null;
|
|
35
|
+
service_domain: string | null;
|
|
36
|
+
received_at: string;
|
|
37
|
+
expires_at: string | null;
|
|
38
|
+
email_id: string;
|
|
39
|
+
}
|
|
40
|
+
export interface CreateAddressInput {
|
|
41
|
+
label?: string;
|
|
42
|
+
name?: string;
|
|
43
|
+
persona?: boolean;
|
|
44
|
+
password?: boolean;
|
|
45
|
+
lifetimeDays?: number;
|
|
46
|
+
domain?: string;
|
|
47
|
+
age?: AgeBand;
|
|
48
|
+
dateOfBirth?: string;
|
|
49
|
+
country?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface AddressPatch {
|
|
52
|
+
label?: string | null;
|
|
53
|
+
notes?: string | null;
|
|
54
|
+
tags?: string[];
|
|
55
|
+
first_name?: string | null;
|
|
56
|
+
last_name?: string | null;
|
|
57
|
+
username?: string | null;
|
|
58
|
+
date_of_birth?: string | null;
|
|
59
|
+
country?: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface Refusal {
|
|
62
|
+
reason: 'too_large' | 'address_limit' | 'daily_limit';
|
|
63
|
+
first_at: string;
|
|
64
|
+
last_at: string;
|
|
65
|
+
count: number;
|
|
66
|
+
size_bytes: number | null;
|
|
67
|
+
}
|
|
68
|
+
export interface DeliveryStatus {
|
|
69
|
+
address: string;
|
|
70
|
+
created_at: string;
|
|
71
|
+
expires_at: string;
|
|
72
|
+
receiving: boolean;
|
|
73
|
+
emails: number;
|
|
74
|
+
last_email_at: string | null;
|
|
75
|
+
refused: Refusal[];
|
|
76
|
+
hint: string;
|
|
77
|
+
help: string;
|
|
78
|
+
}
|
|
79
|
+
export interface Variant {
|
|
80
|
+
tests: string;
|
|
81
|
+
address: string;
|
|
82
|
+
}
|
|
83
|
+
export interface Variants {
|
|
84
|
+
address: string;
|
|
85
|
+
delivered: Variant[];
|
|
86
|
+
undeliverable: Variant[];
|
|
87
|
+
invalid: Variant[];
|
|
88
|
+
}
|
|
89
|
+
export interface WaitOptions {
|
|
90
|
+
timeout?: number;
|
|
91
|
+
service?: string;
|
|
92
|
+
since?: Date | string;
|
|
93
|
+
}
|
|
94
|
+
export type LinkType = 'verify' | 'magic_signin' | 'invite' | 'reset' | 'any';
|
|
95
|
+
export declare class ProxyMailQAError extends Error {
|
|
96
|
+
readonly status: number;
|
|
97
|
+
readonly code: string;
|
|
98
|
+
readonly hint?: string | undefined;
|
|
99
|
+
readonly refused?: Refusal[] | undefined;
|
|
100
|
+
readonly help?: string | undefined;
|
|
101
|
+
constructor(status: number, code: string, message: string, hint?: string | undefined, refused?: Refusal[] | undefined, help?: string | undefined);
|
|
102
|
+
}
|
|
103
|
+
export declare class ProxyMailQA {
|
|
104
|
+
private readonly apiKey;
|
|
105
|
+
private readonly baseUrl;
|
|
106
|
+
private readonly fetcher;
|
|
107
|
+
constructor(options?: ClientOptions);
|
|
108
|
+
private request;
|
|
109
|
+
project(): Promise<Record<string, unknown>>;
|
|
110
|
+
createAddress(input?: CreateAddressInput): Promise<CreatedAddress>;
|
|
111
|
+
listAddresses(options?: {
|
|
112
|
+
limit?: number;
|
|
113
|
+
before?: string;
|
|
114
|
+
}): Promise<Address[]>;
|
|
115
|
+
getAddress(address: string): Promise<Address>;
|
|
116
|
+
updateAddress(address: string, patch: AddressPatch): Promise<Address>;
|
|
117
|
+
deleteAddress(address: string): Promise<void>;
|
|
118
|
+
waitForCode(address: string, options?: WaitOptions): Promise<Match>;
|
|
119
|
+
waitForLink(address: string, options?: WaitOptions & {
|
|
120
|
+
type?: LinkType | LinkType[];
|
|
121
|
+
app?: boolean;
|
|
122
|
+
}): Promise<Match>;
|
|
123
|
+
messages(address: string, options?: {
|
|
124
|
+
includeText?: boolean;
|
|
125
|
+
includeHtml?: boolean;
|
|
126
|
+
limit?: number;
|
|
127
|
+
}): Promise<Record<string, unknown>[]>;
|
|
128
|
+
checkDelivery(address: string): Promise<DeliveryStatus>;
|
|
129
|
+
variants(address: string): Promise<Variants>;
|
|
130
|
+
setPassword(address: string, options?: {
|
|
131
|
+
password?: string;
|
|
132
|
+
label?: string;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
address: string;
|
|
135
|
+
label: string;
|
|
136
|
+
saved: boolean;
|
|
137
|
+
password?: string;
|
|
138
|
+
}>;
|
|
139
|
+
savedPassword(address: string, label?: string): Promise<{
|
|
140
|
+
address: string;
|
|
141
|
+
label: string;
|
|
142
|
+
secret: string;
|
|
143
|
+
}>;
|
|
144
|
+
private wait;
|
|
145
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// A small client for the Proxy Mail QA REST API (https://proxymail.qa/docs/api):
|
|
2
|
+
// create disposable test inboxes and wait for the verification code or link
|
|
3
|
+
// from the email the app under test sends. No dependencies; works in Node 18+,
|
|
4
|
+
// Playwright, Cypress tasks and other runtimes with fetch.
|
|
5
|
+
// An API error. When a wait finds nothing, hint says why as far as Proxy Mail
|
|
6
|
+
// QA can tell (an email provider in sandbox mode, say) and refused lists any
|
|
7
|
+
// mail it turned away; both are in the message too.
|
|
8
|
+
export class ProxyMailQAError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
hint;
|
|
12
|
+
refused;
|
|
13
|
+
help;
|
|
14
|
+
constructor(status, code, message, hint, refused, help) {
|
|
15
|
+
super(hint ? `${message} ${hint}` : message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.hint = hint;
|
|
19
|
+
this.refused = refused;
|
|
20
|
+
this.help = help;
|
|
21
|
+
this.name = 'ProxyMailQAError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const TIMED_OUT = /^No (code|link) arrived/;
|
|
25
|
+
const MAX_WAIT_SECONDS = 30;
|
|
26
|
+
export class ProxyMailQA {
|
|
27
|
+
apiKey;
|
|
28
|
+
baseUrl;
|
|
29
|
+
fetcher;
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
const fromEnv = globalThis.process?.env?.PROXYMAIL_QA_API_KEY;
|
|
32
|
+
const apiKey = options.apiKey ?? fromEnv;
|
|
33
|
+
if (!apiKey)
|
|
34
|
+
throw new Error('Pass apiKey or set PROXYMAIL_QA_API_KEY. Create a key in Project settings → API keys.');
|
|
35
|
+
this.apiKey = apiKey;
|
|
36
|
+
this.baseUrl = (options.baseUrl ?? 'https://api.proxymail.qa').replace(/\/+$/, '');
|
|
37
|
+
this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
38
|
+
}
|
|
39
|
+
async request(method, path, body) {
|
|
40
|
+
const response = await this.fetcher(`${this.baseUrl}/v1${path}`, {
|
|
41
|
+
method,
|
|
42
|
+
headers: { Authorization: `Bearer ${this.apiKey}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) },
|
|
43
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
44
|
+
});
|
|
45
|
+
if (response.status === 204)
|
|
46
|
+
return undefined;
|
|
47
|
+
const payload = (await response.json().catch(() => ({})));
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new ProxyMailQAError(response.status, payload.error ?? 'error', payload.message ?? `Request failed with ${response.status}`, payload.hint, payload.refused, payload.help);
|
|
50
|
+
}
|
|
51
|
+
return payload;
|
|
52
|
+
}
|
|
53
|
+
project() {
|
|
54
|
+
return this.request('GET', '/project');
|
|
55
|
+
}
|
|
56
|
+
createAddress(input = {}) {
|
|
57
|
+
return this.request('POST', '/addresses', {
|
|
58
|
+
label: input.label,
|
|
59
|
+
name: input.name,
|
|
60
|
+
persona: input.persona,
|
|
61
|
+
password: input.password,
|
|
62
|
+
lifetime_days: input.lifetimeDays,
|
|
63
|
+
domain: input.domain,
|
|
64
|
+
age: input.age,
|
|
65
|
+
date_of_birth: input.dateOfBirth,
|
|
66
|
+
country: input.country,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
listAddresses(options = {}) {
|
|
70
|
+
const query = new URLSearchParams();
|
|
71
|
+
if (options.limit)
|
|
72
|
+
query.set('limit', String(options.limit));
|
|
73
|
+
if (options.before)
|
|
74
|
+
query.set('before', options.before);
|
|
75
|
+
return this.request('GET', `/addresses${query.size ? `?${query}` : ''}`);
|
|
76
|
+
}
|
|
77
|
+
getAddress(address) {
|
|
78
|
+
return this.request('GET', `/addresses/${encodeURIComponent(address)}`);
|
|
79
|
+
}
|
|
80
|
+
updateAddress(address, patch) {
|
|
81
|
+
return this.request('PATCH', `/addresses/${encodeURIComponent(address)}`, patch);
|
|
82
|
+
}
|
|
83
|
+
deleteAddress(address) {
|
|
84
|
+
return this.request('DELETE', `/addresses/${encodeURIComponent(address)}`);
|
|
85
|
+
}
|
|
86
|
+
// Waits for a verification or login code; throws ProxyMailQAError (404) if
|
|
87
|
+
// none arrives in time.
|
|
88
|
+
waitForCode(address, options = {}) {
|
|
89
|
+
return this.wait(address, 'code', options);
|
|
90
|
+
}
|
|
91
|
+
// Waits for a verify, magic sign-in, invite or reset link. When an email has
|
|
92
|
+
// both a web link and one that opens a mobile app (myapp://…), the web link
|
|
93
|
+
// comes first: app: true asks for the app one, app: false for a web one.
|
|
94
|
+
waitForLink(address, options = {}) {
|
|
95
|
+
return this.wait(address, 'link', options, [options.type ?? []].flat().join(','), options.app);
|
|
96
|
+
}
|
|
97
|
+
// Emails with their codes and links. includeText and includeHtml add each
|
|
98
|
+
// email's content, for tests that check the email itself; it's written by
|
|
99
|
+
// the sender, so treat it as untrusted. The HTML is the copy cleaned when
|
|
100
|
+
// the email arrived.
|
|
101
|
+
messages(address, options = {}) {
|
|
102
|
+
const query = new URLSearchParams();
|
|
103
|
+
const include = [options.includeText && 'text', options.includeHtml && 'html'].filter(Boolean);
|
|
104
|
+
if (include.length)
|
|
105
|
+
query.set('include', include.join(','));
|
|
106
|
+
if (options.limit)
|
|
107
|
+
query.set('limit', String(options.limit));
|
|
108
|
+
return this.request('GET', `/addresses/${encodeURIComponent(address)}/messages${query.size ? `?${query}` : ''}`);
|
|
109
|
+
}
|
|
110
|
+
// What reached the address and what was refused, without waiting: for when
|
|
111
|
+
// a wait comes back empty.
|
|
112
|
+
checkDelivery(address) {
|
|
113
|
+
return this.request('GET', `/addresses/${encodeURIComponent(address)}/delivery`);
|
|
114
|
+
}
|
|
115
|
+
variants(address) {
|
|
116
|
+
return this.request('GET', `/addresses/${encodeURIComponent(address)}/variants`);
|
|
117
|
+
}
|
|
118
|
+
// Saves the password the test account uses now, after the app under test
|
|
119
|
+
// changed or reset it. Without one, a strong password is generated and
|
|
120
|
+
// returned this once.
|
|
121
|
+
setPassword(address, options = {}) {
|
|
122
|
+
return this.request('PUT', `/addresses/${encodeURIComponent(address)}/password`, { password: options.password, label: options.label });
|
|
123
|
+
}
|
|
124
|
+
// Needs a key created with "Can read saved passwords"; every reveal is logged.
|
|
125
|
+
savedPassword(address, label = 'password') {
|
|
126
|
+
return this.request('GET', `/addresses/${encodeURIComponent(address)}/password?label=${encodeURIComponent(label)}`);
|
|
127
|
+
}
|
|
128
|
+
async wait(address, kind, options, type = '', app) {
|
|
129
|
+
const total = options.timeout ?? MAX_WAIT_SECONDS;
|
|
130
|
+
const deadline = Date.now() + total * 1000;
|
|
131
|
+
const since = options.since === undefined ? undefined : new Date(options.since).toISOString();
|
|
132
|
+
for (;;) {
|
|
133
|
+
const left = Math.max(0, Math.min(MAX_WAIT_SECONDS, Math.ceil((deadline - Date.now()) / 1000)));
|
|
134
|
+
const query = new URLSearchParams({ timeout: String(left) });
|
|
135
|
+
if (options.service)
|
|
136
|
+
query.set('service', options.service);
|
|
137
|
+
if (since)
|
|
138
|
+
query.set('since', since);
|
|
139
|
+
if (type)
|
|
140
|
+
query.set('type', type);
|
|
141
|
+
if (app !== undefined)
|
|
142
|
+
query.set('app', String(app));
|
|
143
|
+
try {
|
|
144
|
+
return await this.request('GET', `/addresses/${encodeURIComponent(address)}/${kind}?${query}`);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const timedOut = error instanceof ProxyMailQAError && error.status === 404 && TIMED_OUT.test(error.message);
|
|
148
|
+
if (!timedOut || Date.now() >= deadline - 500)
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proxymail-qa",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Disposable test inboxes for Playwright, Cypress and scripts: create an address, then wait for its verification code or link.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Proxy Mail QA <hello@proxymail.qa> (https://proxymail.qa)",
|
|
7
|
+
"homepage": "https://proxymail.qa/docs/api",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"email": "hello@proxymail.qa"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"email testing",
|
|
13
|
+
"otp",
|
|
14
|
+
"magic link",
|
|
15
|
+
"email verification",
|
|
16
|
+
"playwright",
|
|
17
|
+
"cypress",
|
|
18
|
+
"qa"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"default": "./dist/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
42
|
+
"prepublishOnly": "pnpm build && pnpm test"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"vitest": "^5.0.3"
|
|
46
|
+
}
|
|
47
|
+
}
|