@hasna/testers 0.0.39 → 0.0.41
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/cli/index.js +90 -12
- package/dist/index.js +47 -7
- package/dist/lib/persona-auth.d.ts +2 -0
- package/dist/lib/persona-auth.d.ts.map +1 -1
- package/dist/lib/persona-redaction.d.ts +19 -0
- package/dist/lib/persona-redaction.d.ts.map +1 -0
- package/dist/mcp/index.js +89 -15
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/server/index.js +48 -8
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -16937,14 +16937,32 @@ function isCredentialReference(value) {
|
|
|
16937
16937
|
var init_secrets_resolver = () => {};
|
|
16938
16938
|
|
|
16939
16939
|
// src/lib/persona-auth.ts
|
|
16940
|
-
function
|
|
16940
|
+
function isSessionCookie(cookieName) {
|
|
16941
|
+
return !/(?:csrf|xsrf)/i.test(cookieName);
|
|
16942
|
+
}
|
|
16943
|
+
function getCookieExpires(cookie) {
|
|
16944
|
+
if (typeof cookie.expires === "number" && Number.isFinite(cookie.expires)) {
|
|
16945
|
+
return cookie.expires;
|
|
16946
|
+
}
|
|
16947
|
+
if (typeof cookie.expires === "string") {
|
|
16948
|
+
const parsed = Number(cookie.expires);
|
|
16949
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
16950
|
+
}
|
|
16951
|
+
return null;
|
|
16952
|
+
}
|
|
16953
|
+
function hasFreshAuthCookies(persona) {
|
|
16941
16954
|
if (!persona.auth?.cookies?.length)
|
|
16942
16955
|
return false;
|
|
16943
16956
|
const cookies = persona.auth.cookies;
|
|
16957
|
+
const sessionCookies = cookies.filter((cookie) => ("name" in cookie) && isSessionCookie(String(cookie.name)));
|
|
16958
|
+
if (sessionCookies.length === 0) {
|
|
16959
|
+
return false;
|
|
16960
|
+
}
|
|
16944
16961
|
const now2 = Date.now() / 1000;
|
|
16945
|
-
const
|
|
16946
|
-
if (
|
|
16947
|
-
return
|
|
16962
|
+
const expiringSessionCookies = sessionCookies.map(getCookieExpires).filter((expires) => expires !== null && expires > 0);
|
|
16963
|
+
if (expiringSessionCookies.length > 0) {
|
|
16964
|
+
return expiringSessionCookies.some((expires) => expires > now2 + 60);
|
|
16965
|
+
}
|
|
16948
16966
|
const updatedAt = new Date(persona.updatedAt).getTime();
|
|
16949
16967
|
return Date.now() - updatedAt < COOKIE_MAX_AGE_MS;
|
|
16950
16968
|
}
|
|
@@ -16982,6 +17000,7 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
16982
17000
|
const loginUrl = auth.loginPath.startsWith("http") ? auth.loginPath : `${baseUrl.replace(/\/$/, "")}${auth.loginPath}`;
|
|
16983
17001
|
try {
|
|
16984
17002
|
await page.goto(loginUrl, { timeout: 30000, waitUntil: "domcontentloaded" });
|
|
17003
|
+
await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {});
|
|
16985
17004
|
} catch (err) {
|
|
16986
17005
|
return { success: false, method: "login", error: `Navigation to login page failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
16987
17006
|
}
|
|
@@ -17038,11 +17057,25 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
17038
17057
|
if (!passwordFilled) {
|
|
17039
17058
|
return { success: false, method: "login", error: "Could not find password field on login page" };
|
|
17040
17059
|
}
|
|
17060
|
+
await page.waitForFunction(() => {
|
|
17061
|
+
const submits = Array.from(document.querySelectorAll('button[type="submit"], input[type="submit"]'));
|
|
17062
|
+
return submits.length === 0 || submits.some((submit) => {
|
|
17063
|
+
const rect = submit.getBoundingClientRect();
|
|
17064
|
+
const visible = rect.width > 0 || rect.height > 0 || submit.getClientRects().length > 0;
|
|
17065
|
+
return visible && !submit.disabled;
|
|
17066
|
+
});
|
|
17067
|
+
}, null, { timeout: 5000 }).catch(() => {});
|
|
17041
17068
|
let submitted = false;
|
|
17042
17069
|
for (const sel of submitSelectors) {
|
|
17043
17070
|
try {
|
|
17044
|
-
const
|
|
17045
|
-
|
|
17071
|
+
const matches = page.locator(sel);
|
|
17072
|
+
const count = await matches.count().catch(() => 0);
|
|
17073
|
+
for (let i = 0;i < Math.min(count, 10); i++) {
|
|
17074
|
+
const el = matches.nth(i);
|
|
17075
|
+
if (!await el.isVisible({ timeout: 500 }).catch(() => false))
|
|
17076
|
+
continue;
|
|
17077
|
+
if (!await el.isEnabled({ timeout: 2000 }).catch(() => false))
|
|
17078
|
+
continue;
|
|
17046
17079
|
await Promise.all([
|
|
17047
17080
|
page.waitForNavigation({ timeout: 15000, waitUntil: "domcontentloaded" }).catch(() => {}),
|
|
17048
17081
|
el.click({ timeout: 5000 })
|
|
@@ -17050,6 +17083,8 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
17050
17083
|
submitted = true;
|
|
17051
17084
|
break;
|
|
17052
17085
|
}
|
|
17086
|
+
if (submitted)
|
|
17087
|
+
break;
|
|
17053
17088
|
} catch {}
|
|
17054
17089
|
}
|
|
17055
17090
|
if (!submitted) {
|
|
@@ -17066,6 +17101,11 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
17066
17101
|
const currentUrl = page.url();
|
|
17067
17102
|
const isStillOnLogin = currentUrl.includes(auth.loginPath) || currentUrl.includes("/login") || currentUrl.includes("/signin") || currentUrl.includes("/auth");
|
|
17068
17103
|
if (isStillOnLogin) {
|
|
17104
|
+
const cookies = await page.context().cookies().catch(() => []);
|
|
17105
|
+
const authCookies = cookies.filter((cookie) => isSessionCookie(cookie.name));
|
|
17106
|
+
if (authCookies.length > 0) {
|
|
17107
|
+
return { success: true, method: "login" };
|
|
17108
|
+
}
|
|
17069
17109
|
let errorText = "";
|
|
17070
17110
|
try {
|
|
17071
17111
|
const errorEl = page.locator('[role="alert"], .error, .alert-error, [data-testid*="error"]').first();
|
|
@@ -17112,7 +17152,7 @@ async function ensurePersonaAuthenticated(page, persona, baseUrl) {
|
|
|
17112
17152
|
if (!persona.auth) {
|
|
17113
17153
|
return { success: true, method: "none" };
|
|
17114
17154
|
}
|
|
17115
|
-
if (
|
|
17155
|
+
if (hasFreshAuthCookies(persona)) {
|
|
17116
17156
|
const restored = await restoreCookies(page, persona);
|
|
17117
17157
|
if (restored) {
|
|
17118
17158
|
return { success: true, method: "cookies" };
|
|
@@ -93997,7 +94037,7 @@ import chalk6 from "chalk";
|
|
|
93997
94037
|
// package.json
|
|
93998
94038
|
var package_default = {
|
|
93999
94039
|
name: "@hasna/testers",
|
|
94000
|
-
version: "0.0.
|
|
94040
|
+
version: "0.0.41",
|
|
94001
94041
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
94002
94042
|
type: "module",
|
|
94003
94043
|
main: "dist/index.js",
|
|
@@ -95724,6 +95764,40 @@ function formatProdDebugPlan(plan) {
|
|
|
95724
95764
|
`);
|
|
95725
95765
|
}
|
|
95726
95766
|
|
|
95767
|
+
// src/lib/persona-redaction.ts
|
|
95768
|
+
function stringKeys(value) {
|
|
95769
|
+
return value ? Object.keys(value).sort() : [];
|
|
95770
|
+
}
|
|
95771
|
+
function cookieNames(cookies) {
|
|
95772
|
+
if (!cookies)
|
|
95773
|
+
return [];
|
|
95774
|
+
return cookies.map((cookie) => cookie.name).filter((name) => typeof name === "string" && name.length > 0);
|
|
95775
|
+
}
|
|
95776
|
+
function redactPersona(persona) {
|
|
95777
|
+
if (!persona.auth)
|
|
95778
|
+
return { ...persona, auth: null };
|
|
95779
|
+
const names = cookieNames(persona.auth.cookies);
|
|
95780
|
+
const headerNames = stringKeys(persona.auth.headers);
|
|
95781
|
+
return {
|
|
95782
|
+
...persona,
|
|
95783
|
+
auth: {
|
|
95784
|
+
emailConfigured: Boolean(persona.auth.email),
|
|
95785
|
+
passwordConfigured: Boolean(persona.auth.password),
|
|
95786
|
+
loginPath: persona.auth.loginPath,
|
|
95787
|
+
strategy: persona.auth.strategy,
|
|
95788
|
+
cookiesConfigured: names.length > 0,
|
|
95789
|
+
cookieCount: names.length,
|
|
95790
|
+
cookieNames: names,
|
|
95791
|
+
headersConfigured: headerNames.length > 0,
|
|
95792
|
+
headerNames,
|
|
95793
|
+
customScriptConfigured: Boolean(persona.auth.customScript)
|
|
95794
|
+
}
|
|
95795
|
+
};
|
|
95796
|
+
}
|
|
95797
|
+
function redactPersonas(personas) {
|
|
95798
|
+
return personas.map(redactPersona);
|
|
95799
|
+
}
|
|
95800
|
+
|
|
95727
95801
|
// src/cli/index.tsx
|
|
95728
95802
|
init_projects();
|
|
95729
95803
|
init_personas();
|
|
@@ -97629,6 +97703,7 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97629
97703
|
process.exit(2);
|
|
97630
97704
|
}, overallTimeoutMs).unref();
|
|
97631
97705
|
}
|
|
97706
|
+
const personaIdList = opts.persona ? opts.persona.split(",").map((s2) => s2.trim()).filter(Boolean) : undefined;
|
|
97632
97707
|
if (!opts.dryRun && !opts.background) {
|
|
97633
97708
|
const budgetResult = checkBudget(0);
|
|
97634
97709
|
if (budgetResult.warning) {
|
|
@@ -97712,7 +97787,9 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97712
97787
|
timeout: opts.timeout ? parseInt(opts.timeout, 10) : undefined,
|
|
97713
97788
|
maxTurns: opts.maxTurns ? parseInt(opts.maxTurns, 10) : undefined,
|
|
97714
97789
|
projectId,
|
|
97715
|
-
engine: opts.browser
|
|
97790
|
+
engine: opts.browser,
|
|
97791
|
+
personaId: personaIdList?.[0],
|
|
97792
|
+
personaIds: personaIdList && personaIdList.length > 1 ? personaIdList : undefined
|
|
97716
97793
|
});
|
|
97717
97794
|
log(chalk6.green(`Run started in background: ${chalk6.bold(runId.slice(0, 8))}`));
|
|
97718
97795
|
log(chalk6.dim(` Scenarios: ${scenarioCount}`));
|
|
@@ -97836,7 +97913,9 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97836
97913
|
samples: parseInt(opts.samples ?? "1", 10),
|
|
97837
97914
|
flakinessThreshold: parseFloat(opts.flakinessThreshold ?? "0.95"),
|
|
97838
97915
|
a11y: opts.a11y ? typeof opts.a11y === "string" ? { level: opts.a11y } : true : undefined,
|
|
97839
|
-
selfHeal: opts.selfHeal || undefined
|
|
97916
|
+
selfHeal: opts.selfHeal || undefined,
|
|
97917
|
+
personaId: personaIdList?.[0],
|
|
97918
|
+
personaIds: personaIdList && personaIdList.length > 1 ? personaIdList : undefined
|
|
97840
97919
|
});
|
|
97841
97920
|
if (opts.json || opts.output) {
|
|
97842
97921
|
const jsonOutput = formatJSON(run3, results2);
|
|
@@ -97924,7 +98003,6 @@ program2.command("run [url] [description]").alias("test").description("Run test
|
|
|
97924
98003
|
log(chalk6.yellow(" --diff: git diff failed. Running all scenarios."));
|
|
97925
98004
|
}
|
|
97926
98005
|
}
|
|
97927
|
-
const personaIdList = opts.persona ? opts.persona.split(",").map((s2) => s2.trim()).filter(Boolean) : undefined;
|
|
97928
98006
|
const { run: run2, results } = await runByFilter({
|
|
97929
98007
|
url: url2,
|
|
97930
98008
|
tags: opts.tag.length > 0 ? opts.tag : undefined,
|
|
@@ -100751,7 +100829,7 @@ personaCmd.command("list").description("List personas").option("--project <id>",
|
|
|
100751
100829
|
globalOnly: opts.global ? true : undefined
|
|
100752
100830
|
});
|
|
100753
100831
|
if (opts.json) {
|
|
100754
|
-
log(JSON.stringify(personas, null, 2));
|
|
100832
|
+
log(JSON.stringify(redactPersonas(personas), null, 2));
|
|
100755
100833
|
return;
|
|
100756
100834
|
}
|
|
100757
100835
|
if (personas.length === 0) {
|
package/dist/index.js
CHANGED
|
@@ -15496,14 +15496,32 @@ function isCredentialReference(value) {
|
|
|
15496
15496
|
|
|
15497
15497
|
// src/lib/persona-auth.ts
|
|
15498
15498
|
var COOKIE_MAX_AGE_MS = 60 * 60 * 1000;
|
|
15499
|
-
function
|
|
15499
|
+
function isSessionCookie(cookieName) {
|
|
15500
|
+
return !/(?:csrf|xsrf)/i.test(cookieName);
|
|
15501
|
+
}
|
|
15502
|
+
function getCookieExpires(cookie) {
|
|
15503
|
+
if (typeof cookie.expires === "number" && Number.isFinite(cookie.expires)) {
|
|
15504
|
+
return cookie.expires;
|
|
15505
|
+
}
|
|
15506
|
+
if (typeof cookie.expires === "string") {
|
|
15507
|
+
const parsed = Number(cookie.expires);
|
|
15508
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
15509
|
+
}
|
|
15510
|
+
return null;
|
|
15511
|
+
}
|
|
15512
|
+
function hasFreshAuthCookies(persona) {
|
|
15500
15513
|
if (!persona.auth?.cookies?.length)
|
|
15501
15514
|
return false;
|
|
15502
15515
|
const cookies = persona.auth.cookies;
|
|
15516
|
+
const sessionCookies = cookies.filter((cookie) => ("name" in cookie) && isSessionCookie(String(cookie.name)));
|
|
15517
|
+
if (sessionCookies.length === 0) {
|
|
15518
|
+
return false;
|
|
15519
|
+
}
|
|
15503
15520
|
const now2 = Date.now() / 1000;
|
|
15504
|
-
const
|
|
15505
|
-
if (
|
|
15506
|
-
return
|
|
15521
|
+
const expiringSessionCookies = sessionCookies.map(getCookieExpires).filter((expires) => expires !== null && expires > 0);
|
|
15522
|
+
if (expiringSessionCookies.length > 0) {
|
|
15523
|
+
return expiringSessionCookies.some((expires) => expires > now2 + 60);
|
|
15524
|
+
}
|
|
15507
15525
|
const updatedAt = new Date(persona.updatedAt).getTime();
|
|
15508
15526
|
return Date.now() - updatedAt < COOKIE_MAX_AGE_MS;
|
|
15509
15527
|
}
|
|
@@ -15541,6 +15559,7 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
15541
15559
|
const loginUrl = auth.loginPath.startsWith("http") ? auth.loginPath : `${baseUrl.replace(/\/$/, "")}${auth.loginPath}`;
|
|
15542
15560
|
try {
|
|
15543
15561
|
await page.goto(loginUrl, { timeout: 30000, waitUntil: "domcontentloaded" });
|
|
15562
|
+
await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {});
|
|
15544
15563
|
} catch (err) {
|
|
15545
15564
|
return { success: false, method: "login", error: `Navigation to login page failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
15546
15565
|
}
|
|
@@ -15597,11 +15616,25 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
15597
15616
|
if (!passwordFilled) {
|
|
15598
15617
|
return { success: false, method: "login", error: "Could not find password field on login page" };
|
|
15599
15618
|
}
|
|
15619
|
+
await page.waitForFunction(() => {
|
|
15620
|
+
const submits = Array.from(document.querySelectorAll('button[type="submit"], input[type="submit"]'));
|
|
15621
|
+
return submits.length === 0 || submits.some((submit) => {
|
|
15622
|
+
const rect = submit.getBoundingClientRect();
|
|
15623
|
+
const visible = rect.width > 0 || rect.height > 0 || submit.getClientRects().length > 0;
|
|
15624
|
+
return visible && !submit.disabled;
|
|
15625
|
+
});
|
|
15626
|
+
}, null, { timeout: 5000 }).catch(() => {});
|
|
15600
15627
|
let submitted = false;
|
|
15601
15628
|
for (const sel of submitSelectors) {
|
|
15602
15629
|
try {
|
|
15603
|
-
const
|
|
15604
|
-
|
|
15630
|
+
const matches = page.locator(sel);
|
|
15631
|
+
const count = await matches.count().catch(() => 0);
|
|
15632
|
+
for (let i = 0;i < Math.min(count, 10); i++) {
|
|
15633
|
+
const el = matches.nth(i);
|
|
15634
|
+
if (!await el.isVisible({ timeout: 500 }).catch(() => false))
|
|
15635
|
+
continue;
|
|
15636
|
+
if (!await el.isEnabled({ timeout: 2000 }).catch(() => false))
|
|
15637
|
+
continue;
|
|
15605
15638
|
await Promise.all([
|
|
15606
15639
|
page.waitForNavigation({ timeout: 15000, waitUntil: "domcontentloaded" }).catch(() => {}),
|
|
15607
15640
|
el.click({ timeout: 5000 })
|
|
@@ -15609,6 +15642,8 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
15609
15642
|
submitted = true;
|
|
15610
15643
|
break;
|
|
15611
15644
|
}
|
|
15645
|
+
if (submitted)
|
|
15646
|
+
break;
|
|
15612
15647
|
} catch {}
|
|
15613
15648
|
}
|
|
15614
15649
|
if (!submitted) {
|
|
@@ -15625,6 +15660,11 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
15625
15660
|
const currentUrl = page.url();
|
|
15626
15661
|
const isStillOnLogin = currentUrl.includes(auth.loginPath) || currentUrl.includes("/login") || currentUrl.includes("/signin") || currentUrl.includes("/auth");
|
|
15627
15662
|
if (isStillOnLogin) {
|
|
15663
|
+
const cookies = await page.context().cookies().catch(() => []);
|
|
15664
|
+
const authCookies = cookies.filter((cookie) => isSessionCookie(cookie.name));
|
|
15665
|
+
if (authCookies.length > 0) {
|
|
15666
|
+
return { success: true, method: "login" };
|
|
15667
|
+
}
|
|
15628
15668
|
let errorText = "";
|
|
15629
15669
|
try {
|
|
15630
15670
|
const errorEl = page.locator('[role="alert"], .error, .alert-error, [data-testid*="error"]').first();
|
|
@@ -15671,7 +15711,7 @@ async function ensurePersonaAuthenticated(page, persona, baseUrl) {
|
|
|
15671
15711
|
if (!persona.auth) {
|
|
15672
15712
|
return { success: true, method: "none" };
|
|
15673
15713
|
}
|
|
15674
|
-
if (
|
|
15714
|
+
if (hasFreshAuthCookies(persona)) {
|
|
15675
15715
|
const restored = await restoreCookies(page, persona);
|
|
15676
15716
|
if (restored) {
|
|
15677
15717
|
return { success: true, method: "cookies" };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
2
|
import type { Persona } from "../types/index.js";
|
|
3
|
+
export declare function isSessionCookie(cookieName: string): boolean;
|
|
3
4
|
export interface LoginResult {
|
|
4
5
|
success: boolean;
|
|
5
6
|
method: "cookies" | "login" | "none";
|
|
6
7
|
error?: string;
|
|
7
8
|
}
|
|
9
|
+
export declare function hasFreshAuthCookies(persona: Persona): boolean;
|
|
8
10
|
/**
|
|
9
11
|
* Perform login using a raw AuthConfig (e.g. from scenario.authConfig or an auth preset).
|
|
10
12
|
* Resolves credentials via resolveCredential() supporting @secrets: and $ENV references.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persona-auth.d.ts","sourceRoot":"","sources":["../../src/lib/persona-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAMjD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"persona-auth.d.ts","sourceRoot":"","sources":["../../src/lib/persona-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAMjD,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkBD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAsB7D;AA2ND;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EACvG,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAgCtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAsBtB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Persona } from "../types/index.js";
|
|
2
|
+
export interface RedactedPersonaAuth {
|
|
3
|
+
emailConfigured: boolean;
|
|
4
|
+
passwordConfigured: boolean;
|
|
5
|
+
loginPath: string;
|
|
6
|
+
strategy: string;
|
|
7
|
+
cookiesConfigured: boolean;
|
|
8
|
+
cookieCount: number;
|
|
9
|
+
cookieNames: string[];
|
|
10
|
+
headersConfigured: boolean;
|
|
11
|
+
headerNames: string[];
|
|
12
|
+
customScriptConfigured: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type RedactedPersona = Omit<Persona, "auth"> & {
|
|
15
|
+
auth: RedactedPersonaAuth | null;
|
|
16
|
+
};
|
|
17
|
+
export declare function redactPersona(persona: Persona): RedactedPersona;
|
|
18
|
+
export declare function redactPersonas(personas: Persona[]): RedactedPersona[];
|
|
19
|
+
//# sourceMappingURL=persona-redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persona-redaction.d.ts","sourceRoot":"","sources":["../../src/lib/persona-redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG;IACpD,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAAC;CAClC,CAAC;AAaF,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAoB/D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,eAAe,EAAE,CAErE"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -52,7 +52,7 @@ var package_default;
|
|
|
52
52
|
var init_package = __esm(() => {
|
|
53
53
|
package_default = {
|
|
54
54
|
name: "@hasna/testers",
|
|
55
|
-
version: "0.0.
|
|
55
|
+
version: "0.0.41",
|
|
56
56
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
57
57
|
type: "module",
|
|
58
58
|
main: "dist/index.js",
|
|
@@ -20045,14 +20045,32 @@ function isCredentialReference(value) {
|
|
|
20045
20045
|
var init_secrets_resolver = () => {};
|
|
20046
20046
|
|
|
20047
20047
|
// src/lib/persona-auth.ts
|
|
20048
|
-
function
|
|
20048
|
+
function isSessionCookie(cookieName) {
|
|
20049
|
+
return !/(?:csrf|xsrf)/i.test(cookieName);
|
|
20050
|
+
}
|
|
20051
|
+
function getCookieExpires(cookie) {
|
|
20052
|
+
if (typeof cookie.expires === "number" && Number.isFinite(cookie.expires)) {
|
|
20053
|
+
return cookie.expires;
|
|
20054
|
+
}
|
|
20055
|
+
if (typeof cookie.expires === "string") {
|
|
20056
|
+
const parsed = Number(cookie.expires);
|
|
20057
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
20058
|
+
}
|
|
20059
|
+
return null;
|
|
20060
|
+
}
|
|
20061
|
+
function hasFreshAuthCookies(persona) {
|
|
20049
20062
|
if (!persona.auth?.cookies?.length)
|
|
20050
20063
|
return false;
|
|
20051
20064
|
const cookies = persona.auth.cookies;
|
|
20065
|
+
const sessionCookies = cookies.filter((cookie) => ("name" in cookie) && isSessionCookie(String(cookie.name)));
|
|
20066
|
+
if (sessionCookies.length === 0) {
|
|
20067
|
+
return false;
|
|
20068
|
+
}
|
|
20052
20069
|
const now2 = Date.now() / 1000;
|
|
20053
|
-
const
|
|
20054
|
-
if (
|
|
20055
|
-
return
|
|
20070
|
+
const expiringSessionCookies = sessionCookies.map(getCookieExpires).filter((expires) => expires !== null && expires > 0);
|
|
20071
|
+
if (expiringSessionCookies.length > 0) {
|
|
20072
|
+
return expiringSessionCookies.some((expires) => expires > now2 + 60);
|
|
20073
|
+
}
|
|
20056
20074
|
const updatedAt = new Date(persona.updatedAt).getTime();
|
|
20057
20075
|
return Date.now() - updatedAt < COOKIE_MAX_AGE_MS;
|
|
20058
20076
|
}
|
|
@@ -20090,6 +20108,7 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
20090
20108
|
const loginUrl = auth.loginPath.startsWith("http") ? auth.loginPath : `${baseUrl.replace(/\/$/, "")}${auth.loginPath}`;
|
|
20091
20109
|
try {
|
|
20092
20110
|
await page.goto(loginUrl, { timeout: 30000, waitUntil: "domcontentloaded" });
|
|
20111
|
+
await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {});
|
|
20093
20112
|
} catch (err) {
|
|
20094
20113
|
return { success: false, method: "login", error: `Navigation to login page failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
20095
20114
|
}
|
|
@@ -20146,11 +20165,25 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
20146
20165
|
if (!passwordFilled) {
|
|
20147
20166
|
return { success: false, method: "login", error: "Could not find password field on login page" };
|
|
20148
20167
|
}
|
|
20168
|
+
await page.waitForFunction(() => {
|
|
20169
|
+
const submits = Array.from(document.querySelectorAll('button[type="submit"], input[type="submit"]'));
|
|
20170
|
+
return submits.length === 0 || submits.some((submit) => {
|
|
20171
|
+
const rect = submit.getBoundingClientRect();
|
|
20172
|
+
const visible = rect.width > 0 || rect.height > 0 || submit.getClientRects().length > 0;
|
|
20173
|
+
return visible && !submit.disabled;
|
|
20174
|
+
});
|
|
20175
|
+
}, null, { timeout: 5000 }).catch(() => {});
|
|
20149
20176
|
let submitted = false;
|
|
20150
20177
|
for (const sel of submitSelectors) {
|
|
20151
20178
|
try {
|
|
20152
|
-
const
|
|
20153
|
-
|
|
20179
|
+
const matches = page.locator(sel);
|
|
20180
|
+
const count = await matches.count().catch(() => 0);
|
|
20181
|
+
for (let i = 0;i < Math.min(count, 10); i++) {
|
|
20182
|
+
const el = matches.nth(i);
|
|
20183
|
+
if (!await el.isVisible({ timeout: 500 }).catch(() => false))
|
|
20184
|
+
continue;
|
|
20185
|
+
if (!await el.isEnabled({ timeout: 2000 }).catch(() => false))
|
|
20186
|
+
continue;
|
|
20154
20187
|
await Promise.all([
|
|
20155
20188
|
page.waitForNavigation({ timeout: 15000, waitUntil: "domcontentloaded" }).catch(() => {}),
|
|
20156
20189
|
el.click({ timeout: 5000 })
|
|
@@ -20158,6 +20191,8 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
20158
20191
|
submitted = true;
|
|
20159
20192
|
break;
|
|
20160
20193
|
}
|
|
20194
|
+
if (submitted)
|
|
20195
|
+
break;
|
|
20161
20196
|
} catch {}
|
|
20162
20197
|
}
|
|
20163
20198
|
if (!submitted) {
|
|
@@ -20174,6 +20209,11 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
20174
20209
|
const currentUrl = page.url();
|
|
20175
20210
|
const isStillOnLogin = currentUrl.includes(auth.loginPath) || currentUrl.includes("/login") || currentUrl.includes("/signin") || currentUrl.includes("/auth");
|
|
20176
20211
|
if (isStillOnLogin) {
|
|
20212
|
+
const cookies = await page.context().cookies().catch(() => []);
|
|
20213
|
+
const authCookies = cookies.filter((cookie) => isSessionCookie(cookie.name));
|
|
20214
|
+
if (authCookies.length > 0) {
|
|
20215
|
+
return { success: true, method: "login" };
|
|
20216
|
+
}
|
|
20177
20217
|
let errorText = "";
|
|
20178
20218
|
try {
|
|
20179
20219
|
const errorEl = page.locator('[role="alert"], .error, .alert-error, [data-testid*="error"]').first();
|
|
@@ -20220,7 +20260,7 @@ async function ensurePersonaAuthenticated(page, persona, baseUrl) {
|
|
|
20220
20260
|
if (!persona.auth) {
|
|
20221
20261
|
return { success: true, method: "none" };
|
|
20222
20262
|
}
|
|
20223
|
-
if (
|
|
20263
|
+
if (hasFreshAuthCookies(persona)) {
|
|
20224
20264
|
const restored = await restoreCookies(page, persona);
|
|
20225
20265
|
if (restored) {
|
|
20226
20266
|
return { success: true, method: "cookies" };
|
|
@@ -53435,6 +53475,40 @@ var init_workflow_agent = __esm(() => {
|
|
|
53435
53475
|
init_runner();
|
|
53436
53476
|
});
|
|
53437
53477
|
|
|
53478
|
+
// src/lib/persona-redaction.ts
|
|
53479
|
+
function stringKeys(value) {
|
|
53480
|
+
return value ? Object.keys(value).sort() : [];
|
|
53481
|
+
}
|
|
53482
|
+
function cookieNames(cookies) {
|
|
53483
|
+
if (!cookies)
|
|
53484
|
+
return [];
|
|
53485
|
+
return cookies.map((cookie) => cookie.name).filter((name21) => typeof name21 === "string" && name21.length > 0);
|
|
53486
|
+
}
|
|
53487
|
+
function redactPersona(persona) {
|
|
53488
|
+
if (!persona.auth)
|
|
53489
|
+
return { ...persona, auth: null };
|
|
53490
|
+
const names = cookieNames(persona.auth.cookies);
|
|
53491
|
+
const headerNames = stringKeys(persona.auth.headers);
|
|
53492
|
+
return {
|
|
53493
|
+
...persona,
|
|
53494
|
+
auth: {
|
|
53495
|
+
emailConfigured: Boolean(persona.auth.email),
|
|
53496
|
+
passwordConfigured: Boolean(persona.auth.password),
|
|
53497
|
+
loginPath: persona.auth.loginPath,
|
|
53498
|
+
strategy: persona.auth.strategy,
|
|
53499
|
+
cookiesConfigured: names.length > 0,
|
|
53500
|
+
cookieCount: names.length,
|
|
53501
|
+
cookieNames: names,
|
|
53502
|
+
headersConfigured: headerNames.length > 0,
|
|
53503
|
+
headerNames,
|
|
53504
|
+
customScriptConfigured: Boolean(persona.auth.customScript)
|
|
53505
|
+
}
|
|
53506
|
+
};
|
|
53507
|
+
}
|
|
53508
|
+
function redactPersonas(personas) {
|
|
53509
|
+
return personas.map(redactPersona);
|
|
53510
|
+
}
|
|
53511
|
+
|
|
53438
53512
|
// src/db/environments.ts
|
|
53439
53513
|
var exports_environments = {};
|
|
53440
53514
|
__export(exports_environments, {
|
|
@@ -86911,7 +86985,7 @@ function buildServer() {
|
|
|
86911
86985
|
authPassword,
|
|
86912
86986
|
authLoginPath
|
|
86913
86987
|
});
|
|
86914
|
-
return json3(persona);
|
|
86988
|
+
return json3(redactPersona(persona));
|
|
86915
86989
|
} catch (error40) {
|
|
86916
86990
|
return errorResponse(error40);
|
|
86917
86991
|
}
|
|
@@ -86923,7 +86997,7 @@ function buildServer() {
|
|
|
86923
86997
|
}, async ({ projectId, enabled, globalOnly }) => {
|
|
86924
86998
|
try {
|
|
86925
86999
|
const personas = listPersonas({ projectId, enabled, globalOnly });
|
|
86926
|
-
return json3({ items: personas, total: personas.length });
|
|
87000
|
+
return json3({ items: redactPersonas(personas), total: personas.length });
|
|
86927
87001
|
} catch (error40) {
|
|
86928
87002
|
return errorResponse(error40);
|
|
86929
87003
|
}
|
|
@@ -86938,7 +87012,7 @@ function buildServer() {
|
|
|
86938
87012
|
const db2 = getDatabase();
|
|
86939
87013
|
const scenarioRows = db2.query("SELECT id, short_id, name FROM scenarios WHERE persona_id = ?").all(persona.id);
|
|
86940
87014
|
return json3({
|
|
86941
|
-
...persona,
|
|
87015
|
+
...redactPersona(persona),
|
|
86942
87016
|
usedByScenarios: scenarioRows.map((r2) => ({ id: r2.id, shortId: r2.short_id, name: r2.name }))
|
|
86943
87017
|
});
|
|
86944
87018
|
} catch (error40) {
|
|
@@ -86961,7 +87035,7 @@ function buildServer() {
|
|
|
86961
87035
|
}, async ({ id, version: version2, ...updates }) => {
|
|
86962
87036
|
try {
|
|
86963
87037
|
const persona = updatePersona(id, updates, version2);
|
|
86964
|
-
return json3(persona);
|
|
87038
|
+
return json3(redactPersona(persona));
|
|
86965
87039
|
} catch (error40) {
|
|
86966
87040
|
return errorResponse(error40, {
|
|
86967
87041
|
fetchCurrent: () => getPersona(id)
|
|
@@ -86992,7 +87066,7 @@ function buildServer() {
|
|
|
86992
87066
|
if (!scenario)
|
|
86993
87067
|
return errorResponse(notFoundErr(scenarioId, "Scenario"));
|
|
86994
87068
|
const updated = updateScenario(scenario.id, { personaId: persona.id }, scenario.version);
|
|
86995
|
-
return json3({ ...updated, attachedPersona: persona });
|
|
87069
|
+
return json3({ ...updated, attachedPersona: redactPersona(persona) });
|
|
86996
87070
|
} catch (error40) {
|
|
86997
87071
|
return errorResponse(error40);
|
|
86998
87072
|
}
|
|
@@ -87390,7 +87464,7 @@ function buildServer() {
|
|
|
87390
87464
|
projectId: scenario.projectId ?? undefined,
|
|
87391
87465
|
personaId: persona.id
|
|
87392
87466
|
});
|
|
87393
|
-
return json3({ ...clone2, attachedPersona: persona, clonedFrom: scenario.id });
|
|
87467
|
+
return json3({ ...clone2, attachedPersona: redactPersona(persona), clonedFrom: scenario.id });
|
|
87394
87468
|
} catch (e2) {
|
|
87395
87469
|
return errorResponse(e2);
|
|
87396
87470
|
}
|
|
@@ -87655,7 +87729,7 @@ Context: ${context2}` : ""}`,
|
|
|
87655
87729
|
const updated = syncPersonaFromContact2(persona.id);
|
|
87656
87730
|
if (!updated)
|
|
87657
87731
|
return json3({ synced: false, message: "No linked contact found or no changes needed" });
|
|
87658
|
-
return json3({ synced: true, persona: updated });
|
|
87732
|
+
return json3({ synced: true, persona: redactPersona(updated) });
|
|
87659
87733
|
} catch (e2) {
|
|
87660
87734
|
return errorResponse(e2);
|
|
87661
87735
|
}
|
package/dist/mcp/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":";AAGA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiHpE,wBAAgB,WAAW,IAAI,SAAS,CA2+EvC"}
|
package/dist/server/index.js
CHANGED
|
@@ -46910,7 +46910,7 @@ import { join as join14 } from "path";
|
|
|
46910
46910
|
// package.json
|
|
46911
46911
|
var package_default = {
|
|
46912
46912
|
name: "@hasna/testers",
|
|
46913
|
-
version: "0.0.
|
|
46913
|
+
version: "0.0.41",
|
|
46914
46914
|
description: "AI-powered QA testing CLI \u2014 spawns cheap AI agents to test web apps with headless browsers",
|
|
46915
46915
|
type: "module",
|
|
46916
46916
|
main: "dist/index.js",
|
|
@@ -48540,14 +48540,32 @@ function resolveCredential(value) {
|
|
|
48540
48540
|
|
|
48541
48541
|
// src/lib/persona-auth.ts
|
|
48542
48542
|
var COOKIE_MAX_AGE_MS = 60 * 60 * 1000;
|
|
48543
|
-
function
|
|
48543
|
+
function isSessionCookie(cookieName) {
|
|
48544
|
+
return !/(?:csrf|xsrf)/i.test(cookieName);
|
|
48545
|
+
}
|
|
48546
|
+
function getCookieExpires(cookie) {
|
|
48547
|
+
if (typeof cookie.expires === "number" && Number.isFinite(cookie.expires)) {
|
|
48548
|
+
return cookie.expires;
|
|
48549
|
+
}
|
|
48550
|
+
if (typeof cookie.expires === "string") {
|
|
48551
|
+
const parsed = Number(cookie.expires);
|
|
48552
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
48553
|
+
}
|
|
48554
|
+
return null;
|
|
48555
|
+
}
|
|
48556
|
+
function hasFreshAuthCookies(persona) {
|
|
48544
48557
|
if (!persona.auth?.cookies?.length)
|
|
48545
48558
|
return false;
|
|
48546
48559
|
const cookies = persona.auth.cookies;
|
|
48560
|
+
const sessionCookies = cookies.filter((cookie) => ("name" in cookie) && isSessionCookie(String(cookie.name)));
|
|
48561
|
+
if (sessionCookies.length === 0) {
|
|
48562
|
+
return false;
|
|
48563
|
+
}
|
|
48547
48564
|
const now2 = Date.now() / 1000;
|
|
48548
|
-
const
|
|
48549
|
-
if (
|
|
48550
|
-
return
|
|
48565
|
+
const expiringSessionCookies = sessionCookies.map(getCookieExpires).filter((expires) => expires !== null && expires > 0);
|
|
48566
|
+
if (expiringSessionCookies.length > 0) {
|
|
48567
|
+
return expiringSessionCookies.some((expires) => expires > now2 + 60);
|
|
48568
|
+
}
|
|
48551
48569
|
const updatedAt = new Date(persona.updatedAt).getTime();
|
|
48552
48570
|
return Date.now() - updatedAt < COOKIE_MAX_AGE_MS;
|
|
48553
48571
|
}
|
|
@@ -48585,6 +48603,7 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
48585
48603
|
const loginUrl = auth.loginPath.startsWith("http") ? auth.loginPath : `${baseUrl.replace(/\/$/, "")}${auth.loginPath}`;
|
|
48586
48604
|
try {
|
|
48587
48605
|
await page.goto(loginUrl, { timeout: 30000, waitUntil: "domcontentloaded" });
|
|
48606
|
+
await page.waitForLoadState("networkidle", { timeout: 1e4 }).catch(() => {});
|
|
48588
48607
|
} catch (err) {
|
|
48589
48608
|
return { success: false, method: "login", error: `Navigation to login page failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
48590
48609
|
}
|
|
@@ -48641,11 +48660,25 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
48641
48660
|
if (!passwordFilled) {
|
|
48642
48661
|
return { success: false, method: "login", error: "Could not find password field on login page" };
|
|
48643
48662
|
}
|
|
48663
|
+
await page.waitForFunction(() => {
|
|
48664
|
+
const submits = Array.from(document.querySelectorAll('button[type="submit"], input[type="submit"]'));
|
|
48665
|
+
return submits.length === 0 || submits.some((submit) => {
|
|
48666
|
+
const rect = submit.getBoundingClientRect();
|
|
48667
|
+
const visible = rect.width > 0 || rect.height > 0 || submit.getClientRects().length > 0;
|
|
48668
|
+
return visible && !submit.disabled;
|
|
48669
|
+
});
|
|
48670
|
+
}, null, { timeout: 5000 }).catch(() => {});
|
|
48644
48671
|
let submitted = false;
|
|
48645
48672
|
for (const sel of submitSelectors) {
|
|
48646
48673
|
try {
|
|
48647
|
-
const
|
|
48648
|
-
|
|
48674
|
+
const matches = page.locator(sel);
|
|
48675
|
+
const count = await matches.count().catch(() => 0);
|
|
48676
|
+
for (let i = 0;i < Math.min(count, 10); i++) {
|
|
48677
|
+
const el = matches.nth(i);
|
|
48678
|
+
if (!await el.isVisible({ timeout: 500 }).catch(() => false))
|
|
48679
|
+
continue;
|
|
48680
|
+
if (!await el.isEnabled({ timeout: 2000 }).catch(() => false))
|
|
48681
|
+
continue;
|
|
48649
48682
|
await Promise.all([
|
|
48650
48683
|
page.waitForNavigation({ timeout: 15000, waitUntil: "domcontentloaded" }).catch(() => {}),
|
|
48651
48684
|
el.click({ timeout: 5000 })
|
|
@@ -48653,6 +48686,8 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
48653
48686
|
submitted = true;
|
|
48654
48687
|
break;
|
|
48655
48688
|
}
|
|
48689
|
+
if (submitted)
|
|
48690
|
+
break;
|
|
48656
48691
|
} catch {}
|
|
48657
48692
|
}
|
|
48658
48693
|
if (!submitted) {
|
|
@@ -48669,6 +48704,11 @@ async function performLogin(page, persona, baseUrl) {
|
|
|
48669
48704
|
const currentUrl = page.url();
|
|
48670
48705
|
const isStillOnLogin = currentUrl.includes(auth.loginPath) || currentUrl.includes("/login") || currentUrl.includes("/signin") || currentUrl.includes("/auth");
|
|
48671
48706
|
if (isStillOnLogin) {
|
|
48707
|
+
const cookies = await page.context().cookies().catch(() => []);
|
|
48708
|
+
const authCookies = cookies.filter((cookie) => isSessionCookie(cookie.name));
|
|
48709
|
+
if (authCookies.length > 0) {
|
|
48710
|
+
return { success: true, method: "login" };
|
|
48711
|
+
}
|
|
48672
48712
|
let errorText = "";
|
|
48673
48713
|
try {
|
|
48674
48714
|
const errorEl = page.locator('[role="alert"], .error, .alert-error, [data-testid*="error"]').first();
|
|
@@ -48715,7 +48755,7 @@ async function ensurePersonaAuthenticated(page, persona, baseUrl) {
|
|
|
48715
48755
|
if (!persona.auth) {
|
|
48716
48756
|
return { success: true, method: "none" };
|
|
48717
48757
|
}
|
|
48718
|
-
if (
|
|
48758
|
+
if (hasFreshAuthCookies(persona)) {
|
|
48719
48759
|
const restored = await restoreCookies(page, persona);
|
|
48720
48760
|
if (restored) {
|
|
48721
48761
|
return { success: true, method: "cookies" };
|