|
1 | | -// Fills a credential input. Arms a route handler that rewrites the form-encoded |
2 | | -// POST body so the Playwright trace records REDACTED-<hash> instead of the |
3 | | -// cleartext value. Only handles application/x-www-form-urlencoded; JSON |
4 | | -// credential submits would need a JSON-aware fixture. |
5 | | -// |
6 | | -// The route handler stays armed for the rest of the page lifecycle: smoke |
7 | | -// specs do `fillPassword(...)` then `Promise.all([waitForURL, click])`. The |
8 | | -// submit POST is intercepted by the closure; we accept the small surface of |
9 | | -// any later form-urlencoded POST also being rewritten because no smoke spec |
10 | | -// makes additional credentialed POSTs after login. |
| 1 | +// Fills a credential input. Currently a thin wrapper around `page.fill` — |
| 2 | +// the historical purpose of this helper was to intercept the credential POST |
| 3 | +// and rewrite it to "REDACTED-<hash>" in the Playwright trace, but Playwright's |
| 4 | +// route.continue() actually modifies the request that reaches the server, which |
| 5 | +// breaks the login. We keep the helper (and the SensitiveValue type) so that |
| 6 | +// callers must explicitly call `.sensitiveValue()` to extract the raw secret, |
| 7 | +// preserving the type-level guarantee that secrets aren't accidentally |
| 8 | +// stringified into logs or assertions. |
11 | 9 |
|
12 | | -import type { Page, Route, Request } from '@playwright/test'; |
13 | | -import { createHash } from 'crypto'; |
| 10 | +import type { Page } from '@playwright/test'; |
14 | 11 |
|
15 | 12 | export interface FillPasswordOptions { |
16 | | - readonly submitUrlContains?: string; |
17 | 13 | readonly fieldNames?: ReadonlyArray<string>; |
18 | 14 | } |
19 | 15 |
|
20 | | -const DEFAULT_FIELDS = ['password', 'pwd'] as const; |
21 | | - |
22 | 16 | export async function fillPassword( |
23 | 17 | page: Page, |
24 | 18 | selector: string, |
25 | 19 | value: string, |
26 | | - opts: FillPasswordOptions = {}, |
| 20 | + _opts: FillPasswordOptions = {}, |
27 | 21 | ): Promise<void> { |
28 | | - const submitUrlContains = opts.submitUrlContains ?? ''; |
29 | | - const fieldNames = opts.fieldNames ?? DEFAULT_FIELDS; |
30 | | - |
31 | | - const redactedFor = (raw: string): string => |
32 | | - `REDACTED-${createHash('sha256').update(raw).digest('hex').slice(0, 16)}`; |
33 | | - |
34 | | - const routeHandler = async (route: Route, req: Request): Promise<void> => { |
35 | | - if (req.method() !== 'POST') return route.continue(); |
36 | | - if (submitUrlContains && !req.url().includes(submitUrlContains)) { |
37 | | - return route.continue(); |
38 | | - } |
39 | | - const ct = req.headers()['content-type'] ?? ''; |
40 | | - const body = req.postData() ?? ''; |
41 | | - if (!ct.includes('application/x-www-form-urlencoded') || !body) { |
42 | | - return route.continue(); |
43 | | - } |
44 | | - const params = new URLSearchParams(body); |
45 | | - let touched = false; |
46 | | - for (const field of fieldNames) { |
47 | | - if (params.has(field)) { |
48 | | - params.set(field, redactedFor(params.get(field) ?? '')); |
49 | | - touched = true; |
50 | | - } |
51 | | - } |
52 | | - if (!touched) return route.continue(); |
53 | | - await route.continue({ postData: params.toString() }); |
54 | | - }; |
55 | | - |
56 | | - await page.route('**/*', routeHandler); |
57 | 22 | await page.fill(selector, value); |
58 | 23 | } |
0 commit comments