@terpjs/conformance 0.10.0 → 0.12.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Page } from "@playwright/test";
1
+ import { type APIResponse, type Page } from "@playwright/test";
2
2
  /**
3
3
  * The administrator the base-profile flows sign in as. The default matches the bundled example
4
4
  * workbench's seed; a generated repo points these at its own seeded admin via
@@ -23,6 +23,52 @@ export declare const VIEWER: {
23
23
  email: string;
24
24
  password: string;
25
25
  };
26
+ /**
27
+ * One 429 the app answered while a spec was running: the throttle, as the server already
28
+ * described it.
29
+ */
30
+ export interface ThrottleRecord {
31
+ /** The request that was refused. */
32
+ url: string;
33
+ /** Seconds until the window resets (`Retry-After`), or null when the header is absent. */
34
+ retryAfter: number | null;
35
+ /** The cap that was hit (`X-RateLimit-Limit`), or null. */
36
+ limit: number | null;
37
+ }
38
+ /**
39
+ * Record the first 429 *page* receives, so a later failure can say what actually happened.
40
+ *
41
+ * The rate limiter is one fixed window keyed by client IP and applied to every request
42
+ * (240/60s by default, health checks included), so a conformance run that logs in, navigates
43
+ * and asserts can reach the cap on a shared runner — and every symptom of that is an element
44
+ * that never appears. The server already says so precisely: a typed `rate_limited` envelope
45
+ * with `Retry-After` and the `X-RateLimit-*` triple. Nothing in this harness read it, so the
46
+ * failure surfaced as a timeout on an unrelated locator, which is a debugging session about
47
+ * the wrong thing.
48
+ *
49
+ * FIRST rather than last: the first refusal is the one that broke the flow, and the ones
50
+ * after it are its consequences.
51
+ */
52
+ export declare function watchForThrottling(page: Page): void;
53
+ /** The throttle *page* saw, if it saw one. */
54
+ export declare function throttleSeen(page: Page): ThrottleRecord | undefined;
55
+ /**
56
+ * Re-throw *error* as a throttle when the page was rate-limited, otherwise unchanged.
57
+ *
58
+ * Wrapping rather than replacing: a 429 seen during a run does not prove it caused this
59
+ * particular failure, so the original error stays in the message. What changes is that the
60
+ * reader is told a throttle happened at all, which is the fact that was on the wire and
61
+ * unread.
62
+ */
63
+ export declare function explainThrottling(page: Page, error: unknown): unknown;
64
+ /**
65
+ * Fail with the throttle named when *response* is a 429, otherwise return it unchanged.
66
+ *
67
+ * The page-level watcher cannot see this one: an `APIRequestContext` emits no response
68
+ * events, so a suite that drives the API directly would still report `expect(response.ok())`
69
+ * as a bare false. The server's own envelope says exactly what happened; this reads it.
70
+ */
71
+ export declare function assertNotThrottled(response: APIResponse): APIResponse;
26
72
  /**
27
73
  * Sign in through the real login screen. App-agnostic: the login screen and session are
28
74
  * base-profile (identical in every Terp app), so this is the reusable entry point any app's
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;CAGjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,IAAI,EACV,WAAW,GAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAU,GACvD,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAItD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,WAAW,EAAE,KAAK,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,KAAK;;;CAGjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF,eAAO,MAAM,MAAM;;;CAGlB,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAkBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAYnD;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,cAAc,GAAG,SAAS,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAarE;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,WAAW,GAAG,WAAW,CAarE;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,IAAI,EACV,WAAW,GAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAU,GACvD,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtD"}
package/dist/index.js CHANGED
@@ -23,6 +23,104 @@ export const VIEWER = {
23
23
  email: process.env.TERP_E2E_VIEWER_EMAIL ?? "viewer@acme.test",
24
24
  password: process.env.TERP_E2E_VIEWER_PASSWORD ?? "correct horse battery staple",
25
25
  };
26
+ /**
27
+ * Every page being watched, and the first throttle each one saw.
28
+ *
29
+ * A WeakMap rather than a field on the page: the harness has no fixture of its own to hang
30
+ * state on, and a suite that opens several pages must not have one page's verdict explain
31
+ * another's.
32
+ */
33
+ const THROTTLES = new WeakMap();
34
+ const WATCHED = new WeakSet();
35
+ function header(value) {
36
+ if (value === undefined)
37
+ return null;
38
+ const parsed = Number(value);
39
+ return Number.isFinite(parsed) ? parsed : null;
40
+ }
41
+ /**
42
+ * Record the first 429 *page* receives, so a later failure can say what actually happened.
43
+ *
44
+ * The rate limiter is one fixed window keyed by client IP and applied to every request
45
+ * (240/60s by default, health checks included), so a conformance run that logs in, navigates
46
+ * and asserts can reach the cap on a shared runner — and every symptom of that is an element
47
+ * that never appears. The server already says so precisely: a typed `rate_limited` envelope
48
+ * with `Retry-After` and the `X-RateLimit-*` triple. Nothing in this harness read it, so the
49
+ * failure surfaced as a timeout on an unrelated locator, which is a debugging session about
50
+ * the wrong thing.
51
+ *
52
+ * FIRST rather than last: the first refusal is the one that broke the flow, and the ones
53
+ * after it are its consequences.
54
+ */
55
+ export function watchForThrottling(page) {
56
+ if (WATCHED.has(page))
57
+ return;
58
+ WATCHED.add(page);
59
+ page.on("response", (response) => {
60
+ if (response.status() !== 429 || THROTTLES.has(page))
61
+ return;
62
+ const headers = response.headers();
63
+ THROTTLES.set(page, {
64
+ url: response.url(),
65
+ retryAfter: header(headers["retry-after"]),
66
+ limit: header(headers["x-ratelimit-limit"]),
67
+ });
68
+ });
69
+ }
70
+ /** The throttle *page* saw, if it saw one. */
71
+ export function throttleSeen(page) {
72
+ return THROTTLES.get(page);
73
+ }
74
+ /**
75
+ * Re-throw *error* as a throttle when the page was rate-limited, otherwise unchanged.
76
+ *
77
+ * Wrapping rather than replacing: a 429 seen during a run does not prove it caused this
78
+ * particular failure, so the original error stays in the message. What changes is that the
79
+ * reader is told a throttle happened at all, which is the fact that was on the wire and
80
+ * unread.
81
+ */
82
+ export function explainThrottling(page, error) {
83
+ const throttle = THROTTLES.get(page);
84
+ if (throttle === undefined)
85
+ return error;
86
+ const wait = throttle.retryAfter === null ? "" : `, Retry-After ${throttle.retryAfter}s`;
87
+ const cap = throttle.limit === null ? "" : ` (cap ${throttle.limit} requests/window)`;
88
+ return new Error(`the run was rate-limited (429${wait})${cap}: ${throttle.url}\n` +
89
+ " One fixed window keyed by client IP covers every request, so a whole suite " +
90
+ "sharing a runner can exhaust it and every symptom looks like a missing element.\n" +
91
+ " Raise the cap for the workbench via SecurityConfig(rate_limit=RateLimit(...)), " +
92
+ "or serialise the suite.\n" +
93
+ ` The assertion that failed: ${error instanceof Error ? error.message : String(error)}`);
94
+ }
95
+ /** Run *body*, and explain a failure as a throttle when the page saw one. */
96
+ async function orThrottle(page, body) {
97
+ try {
98
+ return await body();
99
+ }
100
+ catch (error) {
101
+ throw explainThrottling(page, error);
102
+ }
103
+ }
104
+ /**
105
+ * Fail with the throttle named when *response* is a 429, otherwise return it unchanged.
106
+ *
107
+ * The page-level watcher cannot see this one: an `APIRequestContext` emits no response
108
+ * events, so a suite that drives the API directly would still report `expect(response.ok())`
109
+ * as a bare false. The server's own envelope says exactly what happened; this reads it.
110
+ */
111
+ export function assertNotThrottled(response) {
112
+ if (response.status() !== 429)
113
+ return response;
114
+ const headers = response.headers();
115
+ const retryAfter = headers["retry-after"];
116
+ const limit = headers["x-ratelimit-limit"];
117
+ throw new Error(`the run was rate-limited (429${retryAfter === undefined ? "" : `, Retry-After ${retryAfter}s`})` +
118
+ `${limit === undefined ? "" : ` (cap ${limit} requests/window)`}: ${response.url()}\n` +
119
+ " One fixed window keyed by client IP covers every request, so a whole suite " +
120
+ "sharing a runner can exhaust it.\n" +
121
+ " Raise the cap for the workbench via SecurityConfig(rate_limit=RateLimit(...)), " +
122
+ "or serialise the suite.");
123
+ }
26
124
  /**
27
125
  * Sign in through the real login screen. App-agnostic: the login screen and session are
28
126
  * base-profile (identical in every Terp app), so this is the reusable entry point any app's
@@ -30,14 +128,23 @@ export const VIEWER = {
30
128
  * callers assert their own landing content afterwards.
31
129
  */
32
130
  export async function login(page, credentials = ADMIN) {
33
- await page.goto("/");
34
- await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
35
- await page.getByPlaceholder("Email").fill(credentials.email);
36
- await page.getByPlaceholder("Password").fill(credentials.password);
131
+ watchForThrottling(page);
132
+ await orThrottle(page, async () => {
133
+ await page.goto("/");
134
+ await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
135
+ });
136
+ // By LABEL, not by placeholder. The login fields were placeholder-only until the labels
137
+ // landed, and a placeholder was the only handle they had — which is the same defect from
138
+ // the other side: a name that vanishes the moment a user types is not a name. Selecting
139
+ // the way a user perceives the field is also what keeps this suite honest about
140
+ // accessibility, since a field with no accessible name now fails here rather than being
141
+ // reachable by a workaround.
142
+ await page.getByLabel("Email", { exact: true }).fill(credentials.email);
143
+ await page.getByLabel("Password", { exact: true }).fill(credentials.password);
37
144
  await page.getByRole("button", { name: "Sign in" }).click();
38
- await expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0, {
145
+ await orThrottle(page, () => expect(page.getByRole("heading", { name: "Sign in" })).toHaveCount(0, {
39
146
  timeout: 15_000,
40
- });
147
+ }));
41
148
  }
42
149
  /**
43
150
  * Sign out through the shell's account menu and assert the session is gone. Base-profile: the
@@ -56,8 +163,11 @@ export async function login(page, credentials = ADMIN) {
56
163
  * being a pinned inventory whose renames are release notes.
57
164
  */
58
165
  export async function logout(page) {
59
- await page.locator('[data-terp="user-menu"] [data-terp="menu-trigger"]').click();
60
- await page.getByRole("menuitem", { name: "Sign out" }).click();
61
- await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
166
+ watchForThrottling(page);
167
+ await orThrottle(page, async () => {
168
+ await page.locator('[data-terp="user-menu"] [data-terp="menu-trigger"]').click();
169
+ await page.getByRole("menuitem", { name: "Sign out" }).click();
170
+ await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible();
171
+ });
62
172
  }
63
173
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAa,MAAM,kBAAkB,CAAC;AAErD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,iBAAiB;IAC5D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,8BAA8B;CAChF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,kBAAkB;IAC9D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,8BAA8B;CACjF,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,kBAAkB;IAC9D,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,8BAA8B;CACjF,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,IAAU,EACV,cAAmD,KAAK;IAExD,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrB,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC3E,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC7D,MAAM,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACnE,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC5D,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE;QAC1E,OAAO,EAAE,MAAM;KAChB,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAU;IACrC,MAAM,IAAI,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,KAAK,EAAE,CAAC;IACjF,MAAM,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IAC/D,MAAM,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;AAC7E,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terpjs/conformance",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Terp conformance \u2014 app-agnostic Playwright end-to-end flows over a running Terp stack (base profile: auth gating + session lifecycle) plus the login/role helpers an app's own e2e suite composes.",
6
6
  "main": "./dist/index.js",