visual-mirror 1.0.4 → 1.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/README.md +27 -6
- package/dist/capture.d.ts +9 -1
- package/dist/capture.js +20 -2
- package/dist/capture.js.map +1 -1
- package/dist/index.js +33 -3
- package/dist/index.js.map +1 -1
- package/dist/login.d.ts +27 -0
- package/dist/login.js +177 -0
- package/dist/login.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,11 +25,12 @@ visual-mirror --ref ./reference.png --url http://localhost:3000
|
|
|
25
25
|
|
|
26
26
|
## How It Works
|
|
27
27
|
|
|
28
|
-
1. **
|
|
29
|
-
2. **
|
|
30
|
-
3. **
|
|
31
|
-
4. **
|
|
32
|
-
5. **
|
|
28
|
+
1. **Resolve** — Checks the URL is reachable. If not, probes nearby ports (and common dev ports like `3000`, `5173`, `8080`) and offers to use whichever one is live. Self-signed HTTPS is accepted so local dev servers Just Work.
|
|
29
|
+
2. **Capture** — Takes a headless Chromium screenshot of your live URL via Playwright. If a login form is detected on the page, you're prompted to either log in (fields are auto-detected) or screenshot the login page as-is.
|
|
30
|
+
3. **Diff** — Compares the captured screenshot against your reference image pixel-by-pixel using Jimp, generating a visual diff overlay
|
|
31
|
+
4. **Analyze** — Sends both screenshots to Claude Vision, which identifies specific UI issues (not just "something changed" — it tells you *what* changed)
|
|
32
|
+
5. **Select** — Presents issues in an interactive terminal prompt (like `yay` on Arch) where you pick which ones to get fix suggestions for
|
|
33
|
+
6. **Report** — Generates a self-contained HTML report with all three images, issues, and fix suggestions, then auto-opens it in your browser
|
|
33
34
|
|
|
34
35
|
## Usage
|
|
35
36
|
|
|
@@ -53,6 +54,24 @@ visual-mirror --ref <path> --url <url> [options]
|
|
|
53
54
|
visual-mirror --ref ./designs/homepage.png --url http://localhost:3000 --width 1440 --height 900
|
|
54
55
|
```
|
|
55
56
|
|
|
57
|
+
### Login Detection
|
|
58
|
+
|
|
59
|
+
If the captured URL lands on a login screen, Visual Mirror detects the username, password, and submit selectors and asks whether you want to authenticate before the screenshot is taken:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
⚙ Login screen detected.
|
|
63
|
+
password field: #password
|
|
64
|
+
user field: input[name="email"]
|
|
65
|
+
submit button: button[type="submit"]
|
|
66
|
+
|
|
67
|
+
==> Log in before capturing? (y/N) y
|
|
68
|
+
==> Username / email: me@example.com
|
|
69
|
+
==> Password: ********
|
|
70
|
+
✔ Logged in successfully.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Decline the prompt to screenshot the login page as-is. Credentials are only used for the current run — nothing is stored on disk.
|
|
74
|
+
|
|
56
75
|
### Interactive Flow
|
|
57
76
|
|
|
58
77
|
After analysis, you'll see something like:
|
|
@@ -87,7 +106,7 @@ Then you'll get targeted fix suggestions:
|
|
|
87
106
|
|
|
88
107
|
## Environment Setup
|
|
89
108
|
|
|
90
|
-
|
|
109
|
+
Visual Mirror uses the Claude API for analysis. If `ANTHROPIC_API_KEY` isn't set in your environment, you'll be prompted for one on first run and told how to persist it in your shell's rc file:
|
|
91
110
|
|
|
92
111
|
```bash
|
|
93
112
|
export ANTHROPIC_API_KEY="your-api-key-here"
|
|
@@ -95,6 +114,8 @@ export ANTHROPIC_API_KEY="your-api-key-here"
|
|
|
95
114
|
|
|
96
115
|
Get one at [console.anthropic.com](https://console.anthropic.com).
|
|
97
116
|
|
|
117
|
+
Chromium is installed on-demand via Playwright the first time you run the tool — no manual setup required.
|
|
118
|
+
|
|
98
119
|
## License
|
|
99
120
|
|
|
100
121
|
MIT
|
package/dist/capture.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import type { LoginFields, LoginCredentials } from "./login.js";
|
|
2
|
+
/**
|
|
3
|
+
* Handler the caller provides when a login screen is detected. It receives
|
|
4
|
+
* the detected fields and returns either credentials (to log in and then
|
|
5
|
+
* screenshot the post-login page) or `null` (to screenshot the login page
|
|
6
|
+
* as-is).
|
|
7
|
+
*/
|
|
8
|
+
export type LoginHandler = (fields: LoginFields) => Promise<LoginCredentials | null>;
|
|
9
|
+
export declare function captureScreenshot(url: string, outputDir: string, width: number, height: number, onLoginDetected?: LoginHandler): Promise<string>;
|
package/dist/capture.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { execSync } from "node:child_process";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { detectLoginFields, performLogin } from "./login.js";
|
|
5
6
|
async function ensureChromium() {
|
|
6
7
|
const { chromium } = await import("playwright");
|
|
7
8
|
const execPath = chromium.executablePath();
|
|
@@ -18,7 +19,7 @@ async function ensureChromium() {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
|
-
export async function captureScreenshot(url, outputDir, width, height) {
|
|
22
|
+
export async function captureScreenshot(url, outputDir, width, height, onLoginDetected) {
|
|
22
23
|
await ensureChromium();
|
|
23
24
|
const { chromium } = await import("playwright");
|
|
24
25
|
const screenshotPath = path.join(outputDir, "captured.png");
|
|
@@ -30,8 +31,25 @@ export async function captureScreenshot(url, outputDir, width, height) {
|
|
|
30
31
|
});
|
|
31
32
|
const page = await context.newPage();
|
|
32
33
|
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
|
|
34
|
+
// Detect login screen before taking the screenshot.
|
|
35
|
+
if (onLoginDetected) {
|
|
36
|
+
const fields = await detectLoginFields(page);
|
|
37
|
+
if (fields) {
|
|
38
|
+
const credentials = await onLoginDetected(fields);
|
|
39
|
+
if (credentials) {
|
|
40
|
+
try {
|
|
41
|
+
await performLogin(page, fields, credentials);
|
|
42
|
+
console.log(chalk.green("✔") + " Logged in successfully.");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(chalk.red("Login failed: ") + (err instanceof Error ? err.message : String(err)));
|
|
46
|
+
console.error(chalk.dim(" Continuing with the current page..."));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
33
51
|
await page.screenshot({ path: screenshotPath, fullPage: false });
|
|
34
|
-
console.log(chalk.green("✔") + ` Captured screenshot from ${chalk.cyan(url)}`);
|
|
52
|
+
console.log(chalk.green("✔") + ` Captured screenshot from ${chalk.cyan(page.url())}`);
|
|
35
53
|
}
|
|
36
54
|
finally {
|
|
37
55
|
await browser.close();
|
package/dist/capture.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Command } from "commander";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import https from "node:https";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
import open from "open";
|
|
8
9
|
import { input, password } from "@inquirer/prompts";
|
|
@@ -12,11 +13,13 @@ import { runDiff } from "./diff.js";
|
|
|
12
13
|
import { analyzeScreenshots } from "./analyze.js";
|
|
13
14
|
import { runInteractive } from "./interactive.js";
|
|
14
15
|
import { generateReport } from "./report.js";
|
|
16
|
+
const pkgPath = path.resolve(fileURLToPath(import.meta.url), "..", "..", "package.json");
|
|
17
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
15
18
|
const program = new Command();
|
|
16
19
|
program
|
|
17
20
|
.name("visual-mirror")
|
|
18
21
|
.description("Pixel diffs tell you something changed. Visual Mirror tells you what it means.")
|
|
19
|
-
.version(
|
|
22
|
+
.version(pkg.version)
|
|
20
23
|
.option("--ref <path>", "Path to reference screenshot (PNG/JPG). If omitted, reads from clipboard")
|
|
21
24
|
.requiredOption("--url <url>", "URL to capture and compare")
|
|
22
25
|
.option("--width <number>", "Viewport width", "1280")
|
|
@@ -141,10 +144,37 @@ async function main() {
|
|
|
141
144
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
142
145
|
// Step 1: Resolve URL (check reachability, try alt ports)
|
|
143
146
|
const resolvedUrl = await resolveUrl(opts.url);
|
|
144
|
-
// Step 2: Capture screenshot
|
|
147
|
+
// Step 2: Capture screenshot (with optional login flow)
|
|
148
|
+
const loginHandler = async (fields) => {
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(chalk.yellow("⚙") + " Login screen detected.");
|
|
151
|
+
console.log(chalk.dim(` password field: ${fields.passwordSelector}`));
|
|
152
|
+
if (fields.userSelector) {
|
|
153
|
+
console.log(chalk.dim(` user field: ${fields.userSelector}`));
|
|
154
|
+
}
|
|
155
|
+
if (fields.submitSelector) {
|
|
156
|
+
console.log(chalk.dim(` submit button: ${fields.submitSelector}`));
|
|
157
|
+
}
|
|
158
|
+
console.log();
|
|
159
|
+
const answer = await input({
|
|
160
|
+
message: "Log in before capturing? (y/N)",
|
|
161
|
+
default: "N",
|
|
162
|
+
});
|
|
163
|
+
if (answer.trim().toLowerCase() !== "y") {
|
|
164
|
+
console.log(chalk.dim(" Capturing the login page as-is."));
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const username = fields.userSelector ? await input({ message: "Username / email:" }) : "";
|
|
168
|
+
const pwd = await password({ message: "Password:", mask: "*" });
|
|
169
|
+
if (!pwd.trim()) {
|
|
170
|
+
console.log(chalk.dim(" No password entered. Capturing the login page as-is."));
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return { username, password: pwd };
|
|
174
|
+
};
|
|
145
175
|
let capturedPath;
|
|
146
176
|
try {
|
|
147
|
-
capturedPath = await captureScreenshot(resolvedUrl, outputDir, width, height);
|
|
177
|
+
capturedPath = await captureScreenshot(resolvedUrl, outputDir, width, height, loginHandler);
|
|
148
178
|
}
|
|
149
179
|
catch (err) {
|
|
150
180
|
console.error(chalk.red(`Could not capture screenshot from ${resolvedUrl}`));
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,KAAK,MAAM,YAAY,CAAC;AAC/B,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/login.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Page } from "playwright";
|
|
2
|
+
export type LoginFields = {
|
|
3
|
+
userSelector: string | null;
|
|
4
|
+
passwordSelector: string;
|
|
5
|
+
submitSelector: string | null;
|
|
6
|
+
};
|
|
7
|
+
export type LoginCredentials = {
|
|
8
|
+
username: string;
|
|
9
|
+
password: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Detect a login form on the current page.
|
|
13
|
+
*
|
|
14
|
+
* Heuristics:
|
|
15
|
+
* - A visible `input[type="password"]` is the strongest signal.
|
|
16
|
+
* - Pair it with the nearest visible text/email/username input (if any).
|
|
17
|
+
* - Find a submit button (button[type=submit], input[type=submit], or a
|
|
18
|
+
* button whose text matches login/sign in).
|
|
19
|
+
*
|
|
20
|
+
* Returns `null` if no login form is detected.
|
|
21
|
+
*/
|
|
22
|
+
export declare function detectLoginFields(page: Page): Promise<LoginFields | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Fill the detected login fields and submit the form. Waits for navigation
|
|
25
|
+
* or network activity to settle before returning.
|
|
26
|
+
*/
|
|
27
|
+
export declare function performLogin(page: Page, fields: LoginFields, credentials: LoginCredentials): Promise<void>;
|
package/dist/login.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const USER_HINT_REGEX = /user|email|login|account|phone|mobile/i;
|
|
2
|
+
const SUBMIT_TEXT_REGEX = /log\s*in|sign\s*in|signin|login|continue|submit|enter/i;
|
|
3
|
+
/**
|
|
4
|
+
* Snapshot the page: collect the visible password field, candidate user
|
|
5
|
+
* inputs and candidate submit buttons — each with a pre-computed selector and
|
|
6
|
+
* the metadata we need for ranking in Node.
|
|
7
|
+
*
|
|
8
|
+
* This is the only function that touches the DOM. All ranking logic lives in
|
|
9
|
+
* the module-level helpers below, which keeps `detectLoginFields` trivial.
|
|
10
|
+
*/
|
|
11
|
+
async function snapshotLoginCandidates(page) {
|
|
12
|
+
return await page.evaluate(() => {
|
|
13
|
+
const isVisible = (el) => {
|
|
14
|
+
const style = window.getComputedStyle(el);
|
|
15
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const rect = el.getBoundingClientRect();
|
|
19
|
+
return rect.width > 0 && rect.height > 0;
|
|
20
|
+
};
|
|
21
|
+
const cssPath = (el) => {
|
|
22
|
+
if (el.id)
|
|
23
|
+
return `#${CSS.escape(el.id)}`;
|
|
24
|
+
const parts = [];
|
|
25
|
+
let cur = el;
|
|
26
|
+
while (cur && cur.nodeType === 1 && parts.length < 4) {
|
|
27
|
+
const tagName = cur.tagName;
|
|
28
|
+
let part = tagName.toLowerCase();
|
|
29
|
+
const nm = cur.name;
|
|
30
|
+
if (nm) {
|
|
31
|
+
parts.unshift(`${part}[name="${nm}"]`);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
const parent = cur.parentElement;
|
|
35
|
+
if (parent) {
|
|
36
|
+
const sameTag = Array.from(parent.children).filter((c) => c.tagName === tagName);
|
|
37
|
+
if (sameTag.length > 1)
|
|
38
|
+
part += `:nth-of-type(${sameTag.indexOf(cur) + 1})`;
|
|
39
|
+
}
|
|
40
|
+
parts.unshift(part);
|
|
41
|
+
cur = parent;
|
|
42
|
+
}
|
|
43
|
+
return parts.join(" > ");
|
|
44
|
+
};
|
|
45
|
+
const allInputs = Array.from(document.querySelectorAll("input"));
|
|
46
|
+
const indexOf = (el) => allInputs.indexOf(el);
|
|
47
|
+
const passwordEl = allInputs.find((el) => el.type === "password" && isVisible(el)) || null;
|
|
48
|
+
const userCandidateTypes = new Set(["email", "text", "tel", ""]);
|
|
49
|
+
const userCandidates = allInputs
|
|
50
|
+
.filter((el) => userCandidateTypes.has(el.type) && isVisible(el))
|
|
51
|
+
.map((el) => ({
|
|
52
|
+
selector: cssPath(el),
|
|
53
|
+
type: el.type,
|
|
54
|
+
name: el.name || "",
|
|
55
|
+
id: el.id || "",
|
|
56
|
+
placeholder: el.placeholder || "",
|
|
57
|
+
autocomplete: el.autocomplete || "",
|
|
58
|
+
domIndex: indexOf(el),
|
|
59
|
+
}));
|
|
60
|
+
const form = passwordEl ? passwordEl.closest("form") : null;
|
|
61
|
+
const scope = form || document;
|
|
62
|
+
const submitCandidates = Array.from(scope.querySelectorAll('button[type="submit"], input[type="submit"], button:not([type]), button[type="button"], [role="button"]'))
|
|
63
|
+
.filter(isVisible)
|
|
64
|
+
.map((el) => {
|
|
65
|
+
const tag = el.tagName;
|
|
66
|
+
const type = tag === "BUTTON"
|
|
67
|
+
? el.type || ""
|
|
68
|
+
: tag === "INPUT"
|
|
69
|
+
? el.type || ""
|
|
70
|
+
: "";
|
|
71
|
+
return {
|
|
72
|
+
selector: cssPath(el),
|
|
73
|
+
tag,
|
|
74
|
+
type,
|
|
75
|
+
text: (el.textContent || el.value || "").trim(),
|
|
76
|
+
isExplicitSubmit: (tag === "BUTTON" && type === "submit") || (tag === "INPUT" && type === "submit"),
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
password: passwordEl
|
|
81
|
+
? {
|
|
82
|
+
selector: cssPath(passwordEl),
|
|
83
|
+
type: passwordEl.type,
|
|
84
|
+
name: passwordEl.name || "",
|
|
85
|
+
id: passwordEl.id || "",
|
|
86
|
+
placeholder: passwordEl.placeholder || "",
|
|
87
|
+
autocomplete: passwordEl.autocomplete || "",
|
|
88
|
+
domIndex: indexOf(passwordEl),
|
|
89
|
+
}
|
|
90
|
+
: null,
|
|
91
|
+
userCandidates,
|
|
92
|
+
submitCandidates,
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function matchesUserHint(input) {
|
|
97
|
+
const hay = [input.name, input.id, input.placeholder, input.autocomplete]
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join(" ");
|
|
100
|
+
return USER_HINT_REGEX.test(hay);
|
|
101
|
+
}
|
|
102
|
+
function pickUserField(candidates, password) {
|
|
103
|
+
if (candidates.length === 0)
|
|
104
|
+
return null;
|
|
105
|
+
// 1. Email type wins.
|
|
106
|
+
const email = candidates.find((c) => c.type === "email");
|
|
107
|
+
if (email)
|
|
108
|
+
return email;
|
|
109
|
+
// 2. Hint match by name/id/placeholder/autocomplete.
|
|
110
|
+
const hinted = candidates.find(matchesUserHint);
|
|
111
|
+
if (hinted)
|
|
112
|
+
return hinted;
|
|
113
|
+
// 3. Fallback: nearest candidate that appears before the password field.
|
|
114
|
+
const preceding = candidates
|
|
115
|
+
.filter((c) => c.domIndex < password.domIndex)
|
|
116
|
+
.sort((a, b) => b.domIndex - a.domIndex);
|
|
117
|
+
return preceding[0] || null;
|
|
118
|
+
}
|
|
119
|
+
function pickSubmitButton(candidates) {
|
|
120
|
+
if (candidates.length === 0)
|
|
121
|
+
return null;
|
|
122
|
+
// 1. Explicit submit buttons win.
|
|
123
|
+
const explicit = candidates.find((c) => c.isExplicitSubmit);
|
|
124
|
+
if (explicit)
|
|
125
|
+
return explicit;
|
|
126
|
+
// 2. Button text match.
|
|
127
|
+
const byText = candidates.find((c) => SUBMIT_TEXT_REGEX.test(c.text));
|
|
128
|
+
if (byText)
|
|
129
|
+
return byText;
|
|
130
|
+
// 3. Any button in scope.
|
|
131
|
+
return candidates[0];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Detect a login form on the current page.
|
|
135
|
+
*
|
|
136
|
+
* Heuristics:
|
|
137
|
+
* - A visible `input[type="password"]` is the strongest signal.
|
|
138
|
+
* - Pair it with the nearest visible text/email/username input (if any).
|
|
139
|
+
* - Find a submit button (button[type=submit], input[type=submit], or a
|
|
140
|
+
* button whose text matches login/sign in).
|
|
141
|
+
*
|
|
142
|
+
* Returns `null` if no login form is detected.
|
|
143
|
+
*/
|
|
144
|
+
export async function detectLoginFields(page) {
|
|
145
|
+
const snapshot = await snapshotLoginCandidates(page);
|
|
146
|
+
if (!snapshot.password)
|
|
147
|
+
return null;
|
|
148
|
+
const user = pickUserField(snapshot.userCandidates, snapshot.password);
|
|
149
|
+
const submit = pickSubmitButton(snapshot.submitCandidates);
|
|
150
|
+
return {
|
|
151
|
+
userSelector: user ? user.selector : null,
|
|
152
|
+
passwordSelector: snapshot.password.selector,
|
|
153
|
+
submitSelector: submit ? submit.selector : null,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Fill the detected login fields and submit the form. Waits for navigation
|
|
158
|
+
* or network activity to settle before returning.
|
|
159
|
+
*/
|
|
160
|
+
export async function performLogin(page, fields, credentials) {
|
|
161
|
+
if (fields.userSelector) {
|
|
162
|
+
await page.fill(fields.userSelector, credentials.username);
|
|
163
|
+
}
|
|
164
|
+
await page.fill(fields.passwordSelector, credentials.password);
|
|
165
|
+
const navigationPromise = page
|
|
166
|
+
.waitForLoadState("networkidle", { timeout: 15000 })
|
|
167
|
+
.catch(() => undefined);
|
|
168
|
+
if (fields.submitSelector) {
|
|
169
|
+
await page.click(fields.submitSelector);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Fallback: press Enter in the password field to submit the form.
|
|
173
|
+
await page.press(fields.passwordSelector, "Enter");
|
|
174
|
+
}
|
|
175
|
+
await navigationPromise;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
|