@empiricalrun/playwright-utils 0.47.2 → 0.47.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @empiricalrun/playwright-utils
2
2
 
3
+ ## 0.47.3
4
+
5
+ ### Patch Changes
6
+
7
+ - ceeb466: feat: new provider option in email client
8
+
3
9
  ## 0.47.2
4
10
 
5
11
  ### Patch Changes
package/dist/email.d.ts CHANGED
@@ -1,18 +1,20 @@
1
+ import { type InboxEmail } from "./inbox-client";
1
2
  import { type MailosaurEmail } from "./mailosaur-client";
2
- export type Email = MailosaurEmail;
3
+ export type Email = MailosaurEmail | InboxEmail;
3
4
  type QueryFilter = {
4
5
  from?: string;
5
6
  subject?: string;
6
7
  body?: string;
7
8
  };
9
+ type EmailProvider = "inbox" | "mailosaur";
8
10
  type EmailClientOptions = {
9
11
  timeout?: number;
10
12
  emailId?: string;
13
+ provider?: EmailProvider;
11
14
  };
12
15
  export declare class EmailClient {
13
16
  address: string;
14
17
  timeout: number;
15
- serverId: string;
16
18
  private client;
17
19
  constructor(opts?: EmailClientOptions);
18
20
  getAddress(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/email.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE1E,MAAM,MAAM,KAAK,GAAG,cAAc,CAAC;AAEnC,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAKF,KAAK,kBAAkB,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,SAAa;IACrB,OAAO,CAAC,MAAM,CAAkB;gBAEpB,IAAI,GAAE,kBAAuB;IAwCzC,UAAU,IAAI,MAAM;IAId,YAAY,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;CAGzD"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../src/email.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE1E,MAAM,MAAM,KAAK,GAAG,cAAc,GAAG,UAAU,CAAC;AAEhD,KAAK,WAAW,GAAG;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAMF,KAAK,aAAa,GAAG,OAAO,GAAG,WAAW,CAAC;AAM3C,KAAK,kBAAkB,GAAG;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,aAAa,CAAC;CAC1B,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,MAAM,CAAsB;gBAExB,IAAI,GAAE,kBAAuB;IA+BzC,UAAU,IAAI,MAAM;IAId,YAAY,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;CAGzD"}
package/dist/email.js CHANGED
@@ -2,47 +2,39 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EmailClient = void 0;
4
4
  const dashboard_client_1 = require("@empiricalrun/dashboard-client");
5
+ const inbox_client_1 = require("./inbox-client");
5
6
  const mailosaur_client_1 = require("./mailosaur-client");
6
7
  const DEFAULT_TIMEOUT = 30_000;
7
- const SERVER_ID = "pnyrwq5o";
8
+ const MAILOSAUR_SERVER_ID = "pnyrwq5o";
9
+ const INBOX_EMAIL_DOMAIN = "empiricalrun.email";
8
10
  class EmailClient {
9
11
  address;
10
12
  timeout;
11
- serverId = SERVER_ID;
12
13
  client;
13
14
  constructor(opts = {}) {
14
15
  const { timeout = DEFAULT_TIMEOUT, emailId: knownEmailId } = opts;
15
16
  this.timeout = timeout;
16
- const empiricalApiKey = process.env.EMPIRICALRUN_API_KEY;
17
- if (empiricalApiKey) {
18
- const apiClient = new dashboard_client_1.DashboardAPIClient({
19
- authType: "project-api-key",
20
- });
21
- this.client = new mailosaur_client_1.MailosaurClient({
22
- apiClient,
23
- serverId: this.serverId,
24
- timeout: this.timeout,
25
- });
17
+ const provider = opts.provider || "mailosaur";
18
+ const apiClient = new dashboard_client_1.DashboardAPIClient({
19
+ authType: "project-api-key",
20
+ });
21
+ const emailId = knownEmailId ||
22
+ [...Array(7)].map(() => Math.random().toString(36)[2]).join("");
23
+ if (provider === "inbox") {
24
+ this.client = new inbox_client_1.InboxClient({ apiClient, timeout: this.timeout });
25
+ this.address = emailId.includes("@")
26
+ ? emailId
27
+ : `${emailId}@${INBOX_EMAIL_DOMAIN}`;
26
28
  }
27
29
  else {
28
- const apiKey = process.env.MAILOSAUR_API_KEY;
29
- if (!apiKey) {
30
- throw new Error("Either EMPIRICALRUN_API_KEY or MAILOSAUR_API_KEY must be set");
31
- }
32
30
  this.client = new mailosaur_client_1.MailosaurClient({
33
- apiKey,
34
- serverId: this.serverId,
31
+ apiClient,
32
+ serverId: MAILOSAUR_SERVER_ID,
35
33
  timeout: this.timeout,
36
34
  });
37
- }
38
- const emailId = knownEmailId ||
39
- [...Array(7)].map(() => Math.random().toString(36)[2]).join("");
40
- if (emailId.includes("@")) {
41
- this.address = emailId;
42
- }
43
- else {
44
- const emailDomain = `${this.serverId}.mailosaur.net`;
45
- this.address = `${emailId}@${emailDomain}`;
35
+ this.address = emailId.includes("@")
36
+ ? emailId
37
+ : `${emailId}@${MAILOSAUR_SERVER_ID}.mailosaur.net`;
46
38
  }
47
39
  }
48
40
  getAddress() {
@@ -0,0 +1,32 @@
1
+ import type { IDashboardAPIClient } from "@empiricalrun/shared-types/api/base";
2
+ type Link = {
3
+ text?: string;
4
+ href?: string;
5
+ };
6
+ export type InboxEmail = {
7
+ subject?: string;
8
+ text?: string;
9
+ html?: string;
10
+ links: Link[];
11
+ codes: string[];
12
+ };
13
+ type InboxClientOptions = {
14
+ apiClient: IDashboardAPIClient;
15
+ timeout: number;
16
+ };
17
+ export declare class InboxClient {
18
+ private apiClient;
19
+ private timeout;
20
+ constructor(opts: InboxClientOptions);
21
+ waitForEmail(address: string, filter?: {
22
+ from?: string;
23
+ subject?: string;
24
+ body?: string;
25
+ }): Promise<InboxEmail>;
26
+ private pollForMessage;
27
+ private parseMessage;
28
+ private extractLinks;
29
+ private extractCodes;
30
+ }
31
+ export {};
32
+ //# sourceMappingURL=inbox-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox-client.d.ts","sourceRoot":"","sources":["../src/inbox-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE/E,KAAK,IAAI,GAAG;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAmBF,KAAK,kBAAkB,GAAG;IACxB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,kBAAkB;IAK9B,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1D,OAAO,CAAC,UAAU,CAAC;YASR,cAAc;IAgD5B,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,YAAY;IAwBpB,OAAO,CAAC,YAAY;CAYrB"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InboxClient = void 0;
4
+ const DEFAULT_POLL_DELAY = 1000;
5
+ class InboxClient {
6
+ apiClient;
7
+ timeout;
8
+ constructor(opts) {
9
+ this.apiClient = opts.apiClient;
10
+ this.timeout = opts.timeout;
11
+ }
12
+ async waitForEmail(address, filter) {
13
+ const now = new Date();
14
+ const bufferPeriod = 5_000;
15
+ const receivedAfter = new Date(now.getTime() - bufferPeriod);
16
+ const message = await this.pollForMessage(address, receivedAfter, filter);
17
+ return this.parseMessage(message);
18
+ }
19
+ async pollForMessage(address, receivedAfter, filter) {
20
+ const startTime = Date.now();
21
+ while (true) {
22
+ const params = new URLSearchParams({
23
+ to: address,
24
+ received_after: receivedAfter.toISOString(),
25
+ });
26
+ if (filter?.from)
27
+ params.set("from", filter.from);
28
+ if (filter?.subject)
29
+ params.set("subject", filter.subject);
30
+ if (filter?.body)
31
+ params.set("body", filter.body);
32
+ const path = `/api/messages?${params.toString()}`;
33
+ let result;
34
+ try {
35
+ result = await this.apiClient.callInboxProxy({
36
+ method: "GET",
37
+ path,
38
+ });
39
+ }
40
+ catch (err) {
41
+ console.error("Inbox network error during search:", err);
42
+ if (err instanceof Error && "status" in err && err.status === 401) {
43
+ throw new Error("Email provider error: Authentication error");
44
+ }
45
+ throw new Error("Email provider error: Network request failed");
46
+ }
47
+ if (result.data?.data) {
48
+ return result.data.data;
49
+ }
50
+ const elapsed = Date.now() - startTime;
51
+ if (elapsed + DEFAULT_POLL_DELAY > this.timeout) {
52
+ throw new Error(`Email not received at ${address} within ${this.timeout}ms`);
53
+ }
54
+ await new Promise((resolve) => setTimeout(resolve, DEFAULT_POLL_DELAY));
55
+ }
56
+ }
57
+ parseMessage(message) {
58
+ const links = this.extractLinks(message.html_body, message.text_body);
59
+ const codes = this.extractCodes(message.html_body, message.text_body);
60
+ return {
61
+ subject: message.subject || undefined,
62
+ text: message.text_body || undefined,
63
+ html: message.html_body || undefined,
64
+ links,
65
+ codes,
66
+ };
67
+ }
68
+ extractLinks(html, text) {
69
+ const links = [];
70
+ if (html) {
71
+ const hrefRegex = /<a[^>]+href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
72
+ let match;
73
+ while ((match = hrefRegex.exec(html)) !== null) {
74
+ links.push({ href: match[1], text: match[2]?.replace(/<[^>]*>/g, "") });
75
+ }
76
+ }
77
+ if (text) {
78
+ const urlRegex = /https?:\/\/[^\s<>"')\]]+/g;
79
+ let match;
80
+ while ((match = urlRegex.exec(text)) !== null) {
81
+ if (!links.some((l) => l.href === match[0])) {
82
+ links.push({ href: match[0] });
83
+ }
84
+ }
85
+ }
86
+ return links;
87
+ }
88
+ extractCodes(html, text) {
89
+ const codes = [];
90
+ const content = text || html || "";
91
+ const codeRegex = /\b(\d{4,8})\b/g;
92
+ let match;
93
+ while ((match = codeRegex.exec(content)) !== null) {
94
+ if (!codes.includes(match[1])) {
95
+ codes.push(match[1]);
96
+ }
97
+ }
98
+ return codes;
99
+ }
100
+ }
101
+ exports.InboxClient = InboxClient;
@@ -1,26 +1,5 @@
1
- export interface IDashboardAPIClient {
2
- request<T>(endpoint: string, options: {
3
- params?: Record<string, string>;
4
- body?: Record<string, unknown>;
5
- method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
6
- }): Promise<T>;
7
- callGitHubProxy<T>(opts: {
8
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
9
- url: string;
10
- body?: unknown;
11
- }): Promise<T>;
12
- callMailosaurProxy<T>(opts: {
13
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
14
- path: string;
15
- body?: unknown;
16
- }): Promise<T>;
17
- callWebhookSiteProxy<T>(opts: {
18
- method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
19
- path: string;
20
- body?: unknown;
21
- }): Promise<T>;
22
- getBaseUrl(): string;
23
- }
1
+ export type { IDashboardAPIClient } from "@empiricalrun/shared-types/api/base";
2
+ import type { IDashboardAPIClient } from "@empiricalrun/shared-types/api/base";
24
3
  type Link = {
25
4
  text?: string;
26
5
  href?: string;
@@ -33,18 +12,12 @@ export type MailosaurEmail = {
33
12
  codes: string[];
34
13
  };
35
14
  type MailosaurClientOptions = {
15
+ apiClient: IDashboardAPIClient;
36
16
  serverId: string;
37
17
  timeout: number;
38
- } & ({
39
- apiKey: string;
40
- apiClient?: never;
41
- } | {
42
- apiKey?: never;
43
- apiClient: IDashboardAPIClient;
44
- });
18
+ };
45
19
  export declare class MailosaurClient {
46
- private apiKey?;
47
- private apiClient?;
20
+ private apiClient;
48
21
  private serverId;
49
22
  private timeout;
50
23
  constructor(opts: MailosaurClientOptions);
@@ -59,5 +32,4 @@ export declare class MailosaurClient {
59
32
  private parseEmailResponse;
60
33
  private sleep;
61
34
  }
62
- export {};
63
35
  //# sourceMappingURL=mailosaur-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mailosaur-client.d.ts","sourceRoot":"","sources":["../src/mailosaur-client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,CAAC,EACP,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC/B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;KACrD,GACA,OAAO,CAAC,CAAC,CAAC,CAAC;IACd,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE;QACvB,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;QACpD,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACf,kBAAkB,CAAC,CAAC,EAAE,IAAI,EAAE;QAC1B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;QACpD,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACf,oBAAoB,CAAC,CAAC,EAAE,IAAI,EAAE;QAC5B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;QACpD,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACf,UAAU,IAAI,MAAM,CAAC;CACtB;AAED,KAAK,IAAI,GAAG;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAkCF,KAAK,sBAAsB,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,CACA;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,KAAK,CAAA;CAAE,GACrC;IAAE,MAAM,CAAC,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,mBAAmB,CAAA;CAAE,CACrD,CAAC;AAEF,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,SAAS,CAAC,CAAsB;IACxC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,sBAAsB;YAW1B,WAAW;IA0CnB,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1D,OAAO,CAAC,cAAc,CAAC;YAqBZ,iBAAiB;YA2EjB,cAAc;IA+B5B,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,KAAK;CAGd"}
1
+ {"version":3,"file":"mailosaur-client.d.ts","sourceRoot":"","sources":["../src/mailosaur-client.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE/E,KAAK,IAAI,GAAG;IACV,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAiCF,KAAK,sBAAsB,GAAG;IAC5B,SAAS,EAAE,mBAAmB,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,eAAe;IAC1B,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,sBAAsB;YAM1B,WAAW;IAsBnB,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAC1D,OAAO,CAAC,cAAc,CAAC;YAqBZ,iBAAiB;YA2EjB,cAAc;IA+B5B,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,KAAK;CAGd"}
@@ -1,51 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MailosaurClient = void 0;
4
- const MAILOSAUR_BASE_URL = "https://mailosaur.com/api";
5
4
  const DEFAULT_POLL_DELAY = 1000;
6
5
  class MailosaurClient {
7
- apiKey;
8
6
  apiClient;
9
7
  serverId;
10
8
  timeout;
11
9
  constructor(opts) {
12
10
  this.serverId = opts.serverId;
13
11
  this.timeout = opts.timeout;
14
- if ("apiClient" in opts && opts.apiClient) {
15
- this.apiClient = opts.apiClient;
16
- }
17
- else {
18
- this.apiKey = opts.apiKey;
19
- }
12
+ this.apiClient = opts.apiClient;
20
13
  }
21
14
  async makeRequest(method, path, body) {
22
- if (this.apiClient) {
23
- const result = await this.apiClient.callMailosaurProxy({
24
- method,
25
- path,
26
- body,
27
- });
28
- return {
29
- data: result.data,
30
- headers: result.headers || {},
31
- status: 200,
32
- };
33
- }
34
- const url = `${MAILOSAUR_BASE_URL}${path}`;
35
- const response = await fetch(url, {
15
+ const result = await this.apiClient.callMailosaurProxy({
36
16
  method,
37
- headers: {
38
- Authorization: `Basic ${Buffer.from(`${this.apiKey}:`).toString("base64")}`,
39
- "Content-Type": "application/json",
40
- },
41
- body: body ? JSON.stringify(body) : undefined,
17
+ path,
18
+ body,
42
19
  });
43
- const responseHeaders = {};
44
- response.headers.forEach((value, key) => {
45
- responseHeaders[key] = value;
46
- });
47
- const data = await response.json().catch(() => null);
48
- return { data, headers: responseHeaders, status: response.status };
20
+ return {
21
+ data: result.data,
22
+ headers: result.headers || {},
23
+ status: 200,
24
+ };
49
25
  }
50
26
  async waitForEmail(address, filter) {
51
27
  const now = new Date();
package/docs/email.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Email automation
2
2
 
3
+ ## Providers
4
+
5
+ The `EmailClient` supports two email providers:
6
+
7
+ - **`mailosaur`** (default) — Uses Mailosaur via the dashboard proxy
8
+ - **`inbox`** — Uses our self-hosted Cloudflare Worker on `empiricalrun.email`
9
+
10
+ Both providers are accessed through the dashboard proxy, so only `EMPIRICALRUN_API_KEY` is required.
11
+
3
12
  ## Example usage
4
13
 
5
14
  ### Dynamic email
@@ -12,7 +21,7 @@ import { EmailClient } from "@empiricalrun/playwright-utils";
12
21
  import { expect } from "@playwright/test";
13
22
 
14
23
  const client = new EmailClient();
15
- const address = client.getAddress();
24
+ const address = client.getAddress(); // e.g. abc1234@empiricalrun.email
16
25
 
17
26
  // Input the `address` in the application
18
27
  // that sends the email.
@@ -30,7 +39,7 @@ This uses a known (static) email that can be used to login
30
39
  into an application.
31
40
 
32
41
  This needs an email id (e.g. `test-login-user`). The email id
33
- is appended with the domain (managed internally) to get the full
42
+ is appended with the domain (`empiricalrun.email`) to get the full
34
43
  email address.
35
44
 
36
45
  ```ts
@@ -47,3 +56,23 @@ const email = await client.waitForEmail();
47
56
  // Get login OTP
48
57
  const loginCode = email.codes[0];
49
58
  ```
59
+
60
+ ### Using the inbox provider
61
+
62
+ Pass `provider: "inbox"` to use the self-hosted inbox provider.
63
+
64
+ ```ts
65
+ const client = new EmailClient({ provider: "inbox" });
66
+ ```
67
+
68
+ ### Filtering emails
69
+
70
+ You can filter emails by sender, subject, or body content:
71
+
72
+ ```ts
73
+ const email = await client.waitForEmail({
74
+ from: "noreply@example.com",
75
+ subject: "Verify your email",
76
+ body: "verification code",
77
+ });
78
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.47.2",
3
+ "version": "0.47.3",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -52,7 +52,7 @@
52
52
  "scripts": {
53
53
  "dev": "tsc --build --watch",
54
54
  "build": "tsc --build",
55
- "clean": "tsc --build --clean",
55
+ "clean": "rm -rf dist && tsc --build --clean",
56
56
  "lint": "biome check --unsafe",
57
57
  "test": "vitest run",
58
58
  "test-browser": "npx playwright test",
@@ -1 +1 @@
1
- {"root":["./src/email.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/postgres.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/attachment-cleanup-test-reporter.ts","./src/reporter/attachment-cleanup.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/harness.ts","./src/reporter/ibr-utils.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/lifecycle-events.ts","./src/reporter/local-test.ts","./src/reporter/reporter-state.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/coverage.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
1
+ {"root":["./src/email.ts","./src/inbox-client.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/postgres.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/attachment-cleanup-test-reporter.ts","./src/reporter/attachment-cleanup.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/harness.ts","./src/reporter/ibr-utils.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/lifecycle-events.ts","./src/reporter/local-test.ts","./src/reporter/reporter-state.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/coverage.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}