proxymail-qa 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 +9 -0
- package/dist/index.d.ts +49 -6
- package/dist/index.js +39 -3
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -19,6 +19,9 @@ const { value: link } = await qa.waitForLink(address, { type: 'reset', timeout:
|
|
|
19
19
|
await qa.deleteAddress(address);
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
With `password: true` the result's `password` is typed as a `string` (pass the
|
|
23
|
+
options inline, or `as const`, so TypeScript sees `true`).
|
|
24
|
+
|
|
22
25
|
Waits return the code or link on its own, with the sending service and its
|
|
23
26
|
expiry, never the email's text. They throw `ProxyMailQAError` (status 404) when
|
|
24
27
|
nothing arrives in time.
|
|
@@ -104,6 +107,12 @@ at any time. Retrying won't help until the app can email any address.
|
|
|
104
107
|
Mobile apps (XCUITest, Espresso, Maestro, Flutter, Detox and Appium):
|
|
105
108
|
https://proxymail.qa/docs/mobile
|
|
106
109
|
|
|
110
|
+
## Source
|
|
111
|
+
|
|
112
|
+
https://github.com/aaquib90/proxymail-qa-js. Releases are published to npm from
|
|
113
|
+
GitHub Actions with provenance, so npm shows which commit and workflow built
|
|
114
|
+
each version.
|
|
115
|
+
|
|
107
116
|
## License
|
|
108
117
|
|
|
109
118
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,15 @@ export interface Address {
|
|
|
26
26
|
export interface CreatedAddress extends Address {
|
|
27
27
|
password?: string;
|
|
28
28
|
}
|
|
29
|
+
export interface CreatedAddressWithPassword extends CreatedAddress {
|
|
30
|
+
password: string;
|
|
31
|
+
}
|
|
32
|
+
export interface SavedPassword {
|
|
33
|
+
address: string;
|
|
34
|
+
label: string;
|
|
35
|
+
saved: boolean;
|
|
36
|
+
password?: string;
|
|
37
|
+
}
|
|
29
38
|
export interface Match {
|
|
30
39
|
address: string;
|
|
31
40
|
type: 'code' | 'link';
|
|
@@ -92,6 +101,32 @@ export interface WaitOptions {
|
|
|
92
101
|
since?: Date | string;
|
|
93
102
|
}
|
|
94
103
|
export type LinkType = 'verify' | 'magic_signin' | 'invite' | 'reset' | 'any';
|
|
104
|
+
export interface EmailSummary {
|
|
105
|
+
received_at: string;
|
|
106
|
+
service: string | null;
|
|
107
|
+
service_domain: string | null;
|
|
108
|
+
type: 'code' | 'link' | null;
|
|
109
|
+
subtype: string | null;
|
|
110
|
+
email_id: string;
|
|
111
|
+
}
|
|
112
|
+
export interface EmailCount {
|
|
113
|
+
address: string;
|
|
114
|
+
since: string | null;
|
|
115
|
+
count: number;
|
|
116
|
+
emails: EmailSummary[];
|
|
117
|
+
}
|
|
118
|
+
export interface CountOptions {
|
|
119
|
+
since?: Date | string;
|
|
120
|
+
service?: string;
|
|
121
|
+
type?: 'code' | 'link';
|
|
122
|
+
subtype?: CountSubtype | CountSubtype[];
|
|
123
|
+
}
|
|
124
|
+
export type CountSubtype = 'otp' | 'coupon' | 'verify' | 'magic_signin' | 'invite' | 'reset' | 'other' | 'any';
|
|
125
|
+
export interface NoEmail {
|
|
126
|
+
address: string;
|
|
127
|
+
since: string;
|
|
128
|
+
until: string;
|
|
129
|
+
}
|
|
95
130
|
export declare class ProxyMailQAError extends Error {
|
|
96
131
|
readonly status: number;
|
|
97
132
|
readonly code: string;
|
|
@@ -107,6 +142,9 @@ export declare class ProxyMailQA {
|
|
|
107
142
|
constructor(options?: ClientOptions);
|
|
108
143
|
private request;
|
|
109
144
|
project(): Promise<Record<string, unknown>>;
|
|
145
|
+
createAddress(input: CreateAddressInput & {
|
|
146
|
+
password: true;
|
|
147
|
+
}): Promise<CreatedAddressWithPassword>;
|
|
110
148
|
createAddress(input?: CreateAddressInput): Promise<CreatedAddress>;
|
|
111
149
|
listAddresses(options?: {
|
|
112
150
|
limit?: number;
|
|
@@ -125,16 +163,21 @@ export declare class ProxyMailQA {
|
|
|
125
163
|
includeHtml?: boolean;
|
|
126
164
|
limit?: number;
|
|
127
165
|
}): Promise<Record<string, unknown>[]>;
|
|
166
|
+
countEmails(address: string, options?: CountOptions): Promise<EmailCount>;
|
|
167
|
+
expectNoEmail(address: string, options?: CountOptions & {
|
|
168
|
+
timeout?: number;
|
|
169
|
+
}): Promise<NoEmail>;
|
|
128
170
|
checkDelivery(address: string): Promise<DeliveryStatus>;
|
|
129
171
|
variants(address: string): Promise<Variants>;
|
|
172
|
+
setPassword(address: string, options: {
|
|
173
|
+
password: string;
|
|
174
|
+
label?: string;
|
|
175
|
+
}): Promise<SavedPassword>;
|
|
130
176
|
setPassword(address: string, options?: {
|
|
131
|
-
password?:
|
|
177
|
+
password?: undefined;
|
|
132
178
|
label?: string;
|
|
133
|
-
}): Promise<{
|
|
134
|
-
|
|
135
|
-
label: string;
|
|
136
|
-
saved: boolean;
|
|
137
|
-
password?: string;
|
|
179
|
+
}): Promise<SavedPassword & {
|
|
180
|
+
password: string;
|
|
138
181
|
}>;
|
|
139
182
|
savedPassword(address: string, label?: string): Promise<{
|
|
140
183
|
address: string;
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,19 @@ export class ProxyMailQAError extends Error {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
const TIMED_OUT = /^No (code|link) arrived/;
|
|
25
|
+
function countQuery(options, extra = {}) {
|
|
26
|
+
const query = new URLSearchParams(extra);
|
|
27
|
+
if (options.since !== undefined)
|
|
28
|
+
query.set('since', new Date(options.since).toISOString());
|
|
29
|
+
if (options.service)
|
|
30
|
+
query.set('service', options.service);
|
|
31
|
+
if (options.type)
|
|
32
|
+
query.set('type', options.type);
|
|
33
|
+
const subtype = [options.subtype ?? []].flat().join(',');
|
|
34
|
+
if (subtype)
|
|
35
|
+
query.set('subtype', subtype);
|
|
36
|
+
return query.size ? `?${query}` : '';
|
|
37
|
+
}
|
|
25
38
|
const MAX_WAIT_SECONDS = 30;
|
|
26
39
|
export class ProxyMailQA {
|
|
27
40
|
apiKey;
|
|
@@ -107,6 +120,32 @@ export class ProxyMailQA {
|
|
|
107
120
|
query.set('limit', String(options.limit));
|
|
108
121
|
return this.request('GET', `/addresses/${encodeURIComponent(address)}/messages${query.size ? `?${query}` : ''}`);
|
|
109
122
|
}
|
|
123
|
+
// How many emails reached the address (since a time, from a service, with a
|
|
124
|
+
// code or link), each summarised without its content. Doesn't wait: count
|
|
125
|
+
// after the wait for the first email returns, so a duplicate has had time
|
|
126
|
+
// to arrive. Without since, every email the address has received.
|
|
127
|
+
countEmails(address, options = {}) {
|
|
128
|
+
return this.request('GET', `/addresses/${encodeURIComponent(address)}/count${countQuery(options)}`);
|
|
129
|
+
}
|
|
130
|
+
// Resolves when no matching email arrives within timeout seconds (default
|
|
131
|
+
// 30; longer repeats the request), for checks such as "an unsubscribed user
|
|
132
|
+
// gets nothing". Throws ProxyMailQAError (status 409, code email_arrived)
|
|
133
|
+
// as soon as one does. Pass since as the time of the action, so an email
|
|
134
|
+
// that came before the call still counts; without it, from now.
|
|
135
|
+
async expectNoEmail(address, options = {}) {
|
|
136
|
+
const deadline = Date.now() + (options.timeout ?? MAX_WAIT_SECONDS) * 1000;
|
|
137
|
+
let since = options.since;
|
|
138
|
+
for (;;) {
|
|
139
|
+
const left = Math.max(0, Math.min(MAX_WAIT_SECONDS, Math.ceil((deadline - Date.now()) / 1000)));
|
|
140
|
+
const query = countQuery({ ...options, since }, { timeout: String(left) });
|
|
141
|
+
const result = await this.request('GET', `/addresses/${encodeURIComponent(address)}/none${query}`);
|
|
142
|
+
// The server's since, so later requests watch from the same moment
|
|
143
|
+
// whatever this machine's clock says.
|
|
144
|
+
since = result.since;
|
|
145
|
+
if (Date.now() >= deadline - 500)
|
|
146
|
+
return { address: result.address, since: result.since, until: result.until };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
110
149
|
// What reached the address and what was refused, without waiting: for when
|
|
111
150
|
// a wait comes back empty.
|
|
112
151
|
checkDelivery(address) {
|
|
@@ -115,9 +154,6 @@ export class ProxyMailQA {
|
|
|
115
154
|
variants(address) {
|
|
116
155
|
return this.request('GET', `/addresses/${encodeURIComponent(address)}/variants`);
|
|
117
156
|
}
|
|
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
157
|
setPassword(address, options = {}) {
|
|
122
158
|
return this.request('PUT', `/addresses/${encodeURIComponent(address)}/password`, { password: options.password, label: options.label });
|
|
123
159
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proxymail-qa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
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)",
|
|
7
|
-
"homepage": "https://proxymail.qa/docs/
|
|
7
|
+
"homepage": "https://proxymail.qa/docs/node",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/aaquib90/proxymail-qa-js.git"
|
|
11
|
+
},
|
|
8
12
|
"bugs": {
|
|
13
|
+
"url": "https://github.com/aaquib90/proxymail-qa-js/issues",
|
|
9
14
|
"email": "hello@proxymail.qa"
|
|
10
15
|
},
|
|
11
16
|
"keywords": [
|
|
@@ -39,9 +44,10 @@
|
|
|
39
44
|
"build": "tsc -p tsconfig.build.json",
|
|
40
45
|
"test": "vitest run",
|
|
41
46
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
42
|
-
"prepublishOnly": "
|
|
47
|
+
"prepublishOnly": "npm run build && npm test"
|
|
43
48
|
},
|
|
44
49
|
"devDependencies": {
|
|
50
|
+
"typescript": "^5.6.0",
|
|
45
51
|
"vitest": "^5.0.3"
|
|
46
52
|
}
|
|
47
53
|
}
|