@resolveio/server-lib 22.3.91 → 22.3.92
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/package.json +1 -1
- package/services/codex-client.d.ts +1 -0
- package/services/codex-client.js +27 -26
- package/services/codex-client.js.map +1 -1
- package/util/ai-qa-policy.js +5 -1
- package/util/ai-qa-policy.js.map +1 -1
- package/util/ai-runner-qa-auth.js +66 -12
- package/util/ai-runner-qa-auth.js.map +1 -1
- package/util/ai-runner-qa-tools.js +91 -28
- package/util/ai-runner-qa-tools.js.map +1 -1
|
@@ -27,6 +27,7 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
27
27
|
'const viewportHeight = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_HEIGHT || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_HEIGHT || 1080);',
|
|
28
28
|
'const startupTimeoutMs = Math.max(1000, Number(process.env.RESOLVEIO_RUNNER_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS || process.env.RESOLVEIO_SUPPORT_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS || 900) * 1000);',
|
|
29
29
|
'const resultPath = path.join(artifactDir, "auth-bootstrap-result.json");',
|
|
30
|
+
'const storageStatePath = path.join(artifactDir, "auth-bootstrap-storage-state.json");',
|
|
30
31
|
'const readyScreenshotPath = path.join(artifactDir, "auth-bootstrap-ready.jpg");',
|
|
31
32
|
'const failureScreenshotPath = path.join(artifactDir, "auth-bootstrap-failed.jpg");',
|
|
32
33
|
'',
|
|
@@ -220,6 +221,37 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
220
221
|
' fs.writeFileSync(resultPath, JSON.stringify(payload, null, 2));',
|
|
221
222
|
'}',
|
|
222
223
|
'',
|
|
224
|
+
'function writeAuthStorageState(auth) {',
|
|
225
|
+
' const normalizedUser = normalizeQaUserForBrowser(auth && auth.user);',
|
|
226
|
+
' const bootstrappedAt = new Date().toISOString();',
|
|
227
|
+
' const localStorage = {',
|
|
228
|
+
' refreshToken: auth && auth.refreshToken,',
|
|
229
|
+
' accessToken: auth && auth.accessToken,',
|
|
230
|
+
' user: JSON.stringify(normalizedUser),',
|
|
231
|
+
' lastURL: targetRoute,',
|
|
232
|
+
' "resolveio.runnerQaAuthBootstrappedAt": bootstrappedAt',
|
|
233
|
+
' };',
|
|
234
|
+
' const payload = {',
|
|
235
|
+
' status: "pass",',
|
|
236
|
+
' clientUrl,',
|
|
237
|
+
' serverUrl,',
|
|
238
|
+
' targetRoute,',
|
|
239
|
+
' createdAt: bootstrappedAt,',
|
|
240
|
+
' refreshToken: auth && auth.refreshToken,',
|
|
241
|
+
' accessToken: auth && auth.accessToken,',
|
|
242
|
+
' user: normalizedUser,',
|
|
243
|
+
' localStorage,',
|
|
244
|
+
' cookies: [],',
|
|
245
|
+
' origins: [{',
|
|
246
|
+
' origin: clientUrl,',
|
|
247
|
+
' localStorage: Object.entries(localStorage).map(([name, value]) => ({ name, value: String(value == null ? "" : value) }))',
|
|
248
|
+
' }]',
|
|
249
|
+
' };',
|
|
250
|
+
' fs.mkdirSync(artifactDir, { recursive: true });',
|
|
251
|
+
' fs.writeFileSync(storageStatePath, JSON.stringify(payload, null, 2));',
|
|
252
|
+
' return payload;',
|
|
253
|
+
'}',
|
|
254
|
+
'',
|
|
223
255
|
'function requestJson(url, payload) {',
|
|
224
256
|
' return new Promise((resolve, reject) => {',
|
|
225
257
|
' const body = JSON.stringify(payload || {});',
|
|
@@ -376,6 +408,7 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
376
408
|
'',
|
|
377
409
|
'async function seedAuth(page, auth) {',
|
|
378
410
|
' auth.user = normalizeQaUserForBrowser(auth.user);',
|
|
411
|
+
' writeAuthStorageState(auth);',
|
|
379
412
|
' await page.evaluate((payload) => {',
|
|
380
413
|
' localStorage.setItem("refreshToken", payload.refreshToken);',
|
|
381
414
|
' localStorage.setItem("accessToken", payload.accessToken);',
|
|
@@ -479,8 +512,8 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
479
512
|
' await dismissVisibleTourOrSetupGate(page).catch(() => false);',
|
|
480
513
|
' await patchBrowserQaUser(page);',
|
|
481
514
|
' await delay(500);',
|
|
482
|
-
' const repairedCurrent =
|
|
483
|
-
' return repairedCurrent
|
|
515
|
+
' const repairedCurrent = canonicalizeRoutePath(await page.evaluate(() => location.href));',
|
|
516
|
+
' return routesMatch(repairedCurrent, expectedRoute);',
|
|
484
517
|
'}',
|
|
485
518
|
'',
|
|
486
519
|
'function delay(ms) {',
|
|
@@ -499,8 +532,28 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
499
532
|
' }',
|
|
500
533
|
'}',
|
|
501
534
|
'',
|
|
535
|
+
'function canonicalizeRoutePath(value) {',
|
|
536
|
+
' const route = normalizeRoutePath(value);',
|
|
537
|
+
' const aliases = {',
|
|
538
|
+
' "/billing": "/dashboard/billing",',
|
|
539
|
+
' "/invoice": "/dashboard/billing",',
|
|
540
|
+
' "/invoices": "/dashboard/billing",',
|
|
541
|
+
' "/customer": "/manage/customer",',
|
|
542
|
+
' "/customers": "/manage/customer",',
|
|
543
|
+
' "/pricing": "/manage/pricing",',
|
|
544
|
+
' "/items": "/manage/item",',
|
|
545
|
+
' "/invoice-items": "/manage/item",',
|
|
546
|
+
' "/inventory": "/manage/inventory"',
|
|
547
|
+
' };',
|
|
548
|
+
' return aliases[route] || route;',
|
|
549
|
+
'}',
|
|
550
|
+
'',
|
|
551
|
+
'function routesMatch(currentRoute, expectedRoute) {',
|
|
552
|
+
' return canonicalizeRoutePath(currentRoute) === canonicalizeRoutePath(expectedRoute);',
|
|
553
|
+
'}',
|
|
554
|
+
'',
|
|
502
555
|
'function isGenericAuthTargetRoute(routePath) {',
|
|
503
|
-
' return ["/", "/screen", "/home"].includes(
|
|
556
|
+
' return ["/", "/screen", "/home"].includes(canonicalizeRoutePath(routePath));',
|
|
504
557
|
'}',
|
|
505
558
|
'',
|
|
506
559
|
'function isAuthenticatedNonSetupRoute(summary) {',
|
|
@@ -515,12 +568,12 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
515
568
|
'}',
|
|
516
569
|
'',
|
|
517
570
|
'async function assertTargetRoute(page) {',
|
|
518
|
-
' const expected =
|
|
571
|
+
' const expected = canonicalizeRoutePath(targetRoute);',
|
|
519
572
|
' if (expected === "/") {',
|
|
520
573
|
' return;',
|
|
521
574
|
' }',
|
|
522
|
-
' const current =
|
|
523
|
-
' if (current
|
|
575
|
+
' const current = canonicalizeRoutePath(await page.evaluate(() => location.href));',
|
|
576
|
+
' if (!routesMatch(current, expected)) {',
|
|
524
577
|
' const repaired = await repairPostLoginSetupOrTourGate(page, expected);',
|
|
525
578
|
' if (repaired) {',
|
|
526
579
|
' return;',
|
|
@@ -534,7 +587,7 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
534
587
|
'}',
|
|
535
588
|
'',
|
|
536
589
|
'async function waitForStableTargetRoute(page) {',
|
|
537
|
-
' const expected =
|
|
590
|
+
' const expected = canonicalizeRoutePath(targetRoute);',
|
|
538
591
|
' if (isGenericAuthTargetRoute(expected)) {',
|
|
539
592
|
' return;',
|
|
540
593
|
' }',
|
|
@@ -542,9 +595,9 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
542
595
|
' let stableSince = 0;',
|
|
543
596
|
' let lastCurrent = "";',
|
|
544
597
|
' while (Date.now() < deadline) {',
|
|
545
|
-
' const current =
|
|
598
|
+
' const current = canonicalizeRoutePath(await page.evaluate(() => location.href));',
|
|
546
599
|
' lastCurrent = current;',
|
|
547
|
-
' if (current
|
|
600
|
+
' if (routesMatch(current, expected)) {',
|
|
548
601
|
' stableSince = stableSince || Date.now();',
|
|
549
602
|
' if (Date.now() - stableSince >= Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_STABLE_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_STABLE_MS || 2500)) {',
|
|
550
603
|
' return;',
|
|
@@ -576,12 +629,12 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
576
629
|
'}',
|
|
577
630
|
'',
|
|
578
631
|
'async function assertSummaryOnTargetRoute(summary) {',
|
|
579
|
-
' const expected =
|
|
632
|
+
' const expected = canonicalizeRoutePath(targetRoute);',
|
|
580
633
|
' if (expected === "/") {',
|
|
581
634
|
' return;',
|
|
582
635
|
' }',
|
|
583
|
-
' const current =
|
|
584
|
-
' if (current
|
|
636
|
+
' const current = canonicalizeRoutePath(summary && summary.url || "");',
|
|
637
|
+
' if (!routesMatch(current, expected)) {',
|
|
585
638
|
' if (isGenericAuthTargetRoute(expected) && isAuthenticatedNonSetupRoute(summary)) {',
|
|
586
639
|
' return;',
|
|
587
640
|
' }',
|
|
@@ -672,6 +725,7 @@ function buildResolveIORunnerQaAuthBootstrapScript(options) {
|
|
|
672
725
|
' serverUrl,',
|
|
673
726
|
' targetRoute,',
|
|
674
727
|
' screenshot: readyScreenshotPath,',
|
|
728
|
+
' storageState: storageStatePath,',
|
|
675
729
|
' user: { _id: auth.user && auth.user._id, username: auth.user && auth.user.username, fullname: auth.user && auth.user.fullname },',
|
|
676
730
|
' page: finalPage',
|
|
677
731
|
' };',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/util/ai-runner-qa-auth.ts"],"names":[],"mappings":"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","file":"ai-runner-qa-auth.js","sourcesContent":["export interface ResolveIORunnerQaAuthBootstrapScriptOptions {\n\tdefaultUsername?: string;\n\tdefaultPassword?: string;\n}\n\nexport function buildResolveIORunnerQaAuthBootstrapScript(options: ResolveIORunnerQaAuthBootstrapScriptOptions = {}): string {\n\tconst defaultUsername = options.defaultUsername || 'admin';\n\tconst defaultPassword = options.defaultPassword || '';\n\treturn [\n\t\t'#!/usr/bin/env node',\n\t\t\"'use strict';\",\n\t\t'',\n\t\t'const fs = require(\"fs\");',\n\t\t'const crypto = require(\"crypto\");',\n\t\t'const http = require(\"http\");',\n\t\t'const https = require(\"https\");',\n\t\t'const path = require(\"path\");',\n\t\t'',\n\t\t'const projectRoot = path.resolve(process.argv[2] || process.cwd());',\n\t\t'const routeArg = process.argv[3] || process.env.RESOLVEIO_RUNNER_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_LAST_URL || \"/\";',\n\t\t'const targetRoute = routeArg.startsWith(\"/\") ? routeArg : `/${routeArg}`;',\n\t\t'const clientUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_CLIENT_URL || process.env.RESOLVEIO_SUPPORT_QA_CLIENT_URL || `http://localhost:${process.env.RESOLVEIO_SUPPORT_QA_CLIENT_PORT || \"4200\"}`);',\n\t\t'const serverUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_SERVER_URL || process.env.RESOLVEIO_SUPPORT_QA_SERVER_URL || \"http://localhost:8080\");',\n\t\t`const username = process.env.RESOLVEIO_RUNNER_QA_USERNAME || process.env.RESOLVEIO_SUPPORT_QA_USERNAME || ${JSON.stringify(defaultUsername)};`,\n\t\t`const password = process.env.RESOLVEIO_RUNNER_QA_PASSWORD || process.env.RESOLVEIO_SUPPORT_QA_PASSWORD || ${JSON.stringify(defaultPassword)};`,\n\t\t'const artifactDir = path.resolve(process.env.RESOLVEIO_RUNNER_QA_ARTIFACT_DIR || process.env.RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR || path.join(projectRoot, \"qa-artifacts\"));',\n\t\t'const viewportWidth = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_WIDTH || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_WIDTH || 1920);',\n\t\t'const viewportHeight = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_HEIGHT || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_HEIGHT || 1080);',\n\t\t'const startupTimeoutMs = Math.max(1000, Number(process.env.RESOLVEIO_RUNNER_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS || process.env.RESOLVEIO_SUPPORT_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS || 900) * 1000);',\n\t\t'const resultPath = path.join(artifactDir, \"auth-bootstrap-result.json\");',\n\t\t'const readyScreenshotPath = path.join(artifactDir, \"auth-bootstrap-ready.jpg\");',\n\t\t'const failureScreenshotPath = path.join(artifactDir, \"auth-bootstrap-failed.jpg\");',\n\t\t'',\n\t\t'function stripTrailingSlash(value) {',\n\t\t'\treturn String(value || \"\").replace(/\\\\/+$/, \"\");',\n\t\t'}',\n\t\t'',\n\t\t'function isLocalhostUrl(value) {',\n\t\t'\ttry {',\n\t\t'\t\tconst parsed = new URL(value);',\n\t\t'\t\treturn [\"localhost\", \"127.0.0.1\", \"::1\"].includes(parsed.hostname);',\n\t\t'\t}',\n\t\t'\tcatch (error) {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'function resolveLocalMongoUrl() {',\n\t\t'\tconst localQaMongoUrl = process.env.RESOLVEIO_RUNNER_QA_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_MONGO_URL || `mongodb://127.0.0.1:${process.env.RESOLVEIO_SUPPORT_QA_MONGO_PORT || \"3001\"}/resolveio?directConnection=true`;',\n\t\t'\tif (isLocalhostUrl(serverUrl)) {',\n\t\t'\t\treturn localQaMongoUrl;',\n\t\t'\t}',\n\t\t'\treturn process.env.MONGO_URL || localQaMongoUrl;',\n\t\t'}',\n\t\t'',\n\t\t'function requireMongoClient() {',\n\t\t'\tconst candidates = [',\n\t\t'\t\tpath.join(projectRoot, \"server\", \"node_modules\", \"mongodb\"),',\n\t\t'\t\tpath.join(projectRoot, \"node_modules\", \"mongodb\"),',\n\t\t'\t\tpath.join(process.cwd(), \"server\", \"node_modules\", \"mongodb\"),',\n\t\t'\t\tpath.join(process.cwd(), \"node_modules\", \"mongodb\"),',\n\t\t'\t\t\"mongodb\"',\n\t\t'\t];',\n\t\t'\tfor (const candidate of candidates) {',\n\t\t'\t\ttry { return require(candidate).MongoClient; }',\n\t\t'\t\tcatch (error) {}',\n\t\t'\t}',\n\t\t'\tthrow new Error(\"Unable to require mongodb from project/server node_modules or global resolution\");',\n\t\t'}',\n\t\t'',\n\t\t'function buildDefaultQaUserSettings() {',\n\t\t'\treturn {',\n\t\t'\t\ttable_color: \"#3b3ee3\",',\n\t\t'\t\ttable_font_color: \"#ffffff\",',\n\t\t'\t\tsecondary_table_color: \"#87ceeb\",',\n\t\t'\t\tsecondary_table_font_color: \"#000000\",',\n\t\t'\t\ttertiary_table_color: \"#ff4500\",',\n\t\t'\t\ttertiary_table_font_color: \"#000000\",',\n\t\t'\t\tfont_size: 12,',\n\t\t'\t\tcollapsable_menu: true,',\n\t\t'\t\tentries_per_page: \"25\",',\n\t\t'\t\twarning_color: \"#ffc107\",',\n\t\t'\t\twarning_font_color: \"#000000\",',\n\t\t'\t\twarning_hover_color: \"#e0a800\",',\n\t\t'\t\tsuccess_color: \"#28a745\",',\n\t\t'\t\tsuccess_font_color: \"#ffffff\",',\n\t\t'\t\tsuccess_hover_color: \"#218838\",',\n\t\t'\t\tdanger_color: \"#dc3545\",',\n\t\t'\t\tdanger_font_color: \"#ffffff\",',\n\t\t'\t\tdanger_hover_color: \"#c82333\",',\n\t\t'\t\tinfo_color: \"#17a2b8\",',\n\t\t'\t\tinfo_font_color: \"#ffffff\",',\n\t\t'\t\tinfo_hover_color: \"#138496\",',\n\t\t'\t\tprimary_color: \"#007bff\",',\n\t\t'\t\tprimary_font_color: \"#ffffff\",',\n\t\t'\t\tprimary_hover_color: \"#0069d9\",',\n\t\t'\t\tsecondary_color: \"#868e96\",',\n\t\t'\t\tsecondary_font_color: \"#ffffff\",',\n\t\t'\t\tsecondary_hover_color: \"#5a6268\",',\n\t\t'\t\trouting_preference: \"\",',\n\t\t'\t\topening_route: targetRoute,',\n\t\t'\t\trio_select_search_mode: \"exact\"',\n\t\t'\t};',\n\t\t'}',\n\t\t'',\n\t\t'function normalizeQaUserForBrowser(user) {',\n\t\t'\tconst normalized = { ...(user || {}) };',\n\t\t'\tnormalized.other = {',\n\t\t'\t\t...(normalized.other || {}),',\n\t\t'\t\tyards: Array.isArray(normalized.other && normalized.other.yards) ? normalized.other.yards : [],',\n\t\t'\t\ttour_completed: true,',\n\t\t'\t\ttook_tour: true,',\n\t\t'\t\tcore_tour_completed: true,',\n\t\t'\t\twelcome_tour_completed: true,',\n\t\t'\t\ttop_navigation_tour_completed: true,',\n\t\t'\t\tuser_settings_tour_completed: true',\n\t\t'\t};',\n\t\t'\tnormalized.settings = {',\n\t\t'\t\t...buildDefaultQaUserSettings(),',\n\t\t'\t\t...(normalized.settings || {}),',\n\t\t'\t\topening_route: targetRoute',\n\t\t'\t};',\n\t\t'\treturn normalized;',\n\t\t'}',\n\t\t'',\n\t\t'async function ensureLocalQaUser() {',\n\t\t'\tif (!isLocalhostUrl(serverUrl) || process.env.RESOLVEIO_RUNNER_QA_DISABLE_LOCAL_USER_REPAIR === \"true\" || process.env.RESOLVEIO_SUPPORT_QA_DISABLE_LOCAL_USER_REPAIR === \"true\") {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'\tconst MongoClient = requireMongoClient();',\n\t\t'\tconst client = new MongoClient(resolveLocalMongoUrl());',\n\t\t'\tawait client.connect();',\n\t\t'\ttry {',\n\t\t'\t\tconst db = client.db();',\n\t\t'\t\tconst users = db.collection(\"users\");',\n\t\t'\t\tconst now = new Date();',\n\t\t'\t\tconst salt = crypto.randomBytes(32).toString(\"hex\");',\n\t\t'\t\tconst hash = crypto.pbkdf2Sync(password, salt, 25000, 512, \"sha256\").toString(\"hex\");',\n\t\t'\t\tconst existing = await users.findOne({ $or: [{ username }, { email: username }] }, { projection: { _id: 1 } });',\n\t\t'\t\tconst defaultSettings = buildDefaultQaUserSettings();',\n\t\t'\t\tconst defaultOther = { yards: [], date_picker_day_start: \"S\", tour_completed: true, took_tour: true, core_tour_completed: true, welcome_tour_completed: true, top_navigation_tour_completed: true, user_settings_tour_completed: true, tour_completed_at: now.toISOString() };',\n\t\t'\t\tconst baseUser = {',\n\t\t'\t\t\t__v: 0,',\n\t\t'\t\t\tusername,',\n\t\t'\t\t\temail: username.includes(\"@\") ? username : \"dev@resolveio.com\",',\n\t\t'\t\t\tfullname: \"QA Admin\",',\n\t\t'\t\t\troles: { super_admin: true, approvals: [], groups: [], notifications: [], miscs: [] },',\n\t\t'\t\t\tactive: true,',\n\t\t'\t\t\tother: defaultOther,',\n\t\t'\t\t\tsettings: defaultSettings,',\n\t\t'\t\t\tphonenumber: \"\",',\n\t\t'\t\t\treadonly: false,',\n\t\t'\t\t\tis_customer: false,',\n\t\t'\t\t\thash,',\n\t\t'\t\t\tsalt,',\n\t\t'\t\t\tattempts: 0,',\n\t\t'\t\t\tservices: {},',\n\t\t'\t\t\tupdatedAt: now',\n\t\t'\t\t};',\n\t\t'\t\tconst qaUserFilter = {',\n\t\t'\t\t\t$or: [',\n\t\t'\t\t\t\t{ username },',\n\t\t'\t\t\t\t{ email: username },',\n\t\t'\t\t\t\t{ username: \"admin\" },',\n\t\t'\t\t\t\t{ email: \"admin\" },',\n\t\t'\t\t\t\t{ email: \"dev@resolveio.com\" }',\n\t\t'\t\t\t]',\n\t\t'\t\t};',\n\t\t'\t\tawait users.updateMany(qaUserFilter, {',\n\t\t'\t\t\t$set: {',\n\t\t'\t\t\t\tactive: true,',\n\t\t'\t\t\t\troles: baseUser.roles,',\n\t\t'\t\t\t\tother: defaultOther,',\n\t\t'\t\t\t\tsettings: defaultSettings,',\n\t\t'\t\t\t\thash,',\n\t\t'\t\t\t\tsalt,',\n\t\t'\t\t\t\tattempts: 0,',\n\t\t'\t\t\t\treadonly: false,',\n\t\t'\t\t\t\tis_customer: false,',\n\t\t'\t\t\t\tupdatedAt: now',\n\t\t'\t\t\t}',\n\t\t'\t\t});',\n\t\t'\t\tif (existing && existing._id) {',\n\t\t'\t\t\tawait users.updateOne({ _id: existing._id }, { $set: baseUser });',\n\t\t'\t\t}',\n\t\t'\t\telse {',\n\t\t'\t\t\tawait users.insertOne({ _id: crypto.randomBytes(12).toString(\"hex\"), createdAt: now, ...baseUser });',\n\t\t'\t\t}',\n\t\t'\t\tconst repaired = await users.findOne(qaUserFilter, { projection: { _id: 1 } });',\n\t\t'\t\tif (repaired && repaired._id) {',\n\t\t'\t\t\tawait users.updateOne({ _id: repaired._id }, {',\n\t\t'\t\t\t\t$set: {',\n\t\t'\t\t\t\t\tactive: true,',\n\t\t'\t\t\t\t\troles: baseUser.roles,',\n\t\t'\t\t\t\t\tother: defaultOther,',\n\t\t'\t\t\t\t\tsettings: defaultSettings,',\n\t\t'\t\t\t\t\thash,',\n\t\t'\t\t\t\t\tsalt,',\n\t\t'\t\t\t\t\tattempts: 0,',\n\t\t'\t\t\t\t\treadonly: false,',\n\t\t'\t\t\t\t\tis_customer: false,',\n\t\t'\t\t\t\t\tupdatedAt: now',\n\t\t'\t\t\t\t}',\n\t\t'\t\t\t});',\n\t\t'\t\t}',\n\t\t'\t\treturn true;',\n\t\t'\t}',\n\t\t'\tfinally {',\n\t\t'\t\tawait client.close().catch(() => undefined);',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'async function refreshAuthAndSeed(page) {',\n\t\t'\tconst auth = await login();',\n\t\t'\tawait seedAuth(page, auth);',\n\t\t'\treturn auth;',\n\t\t'}',\n\t\t'',\n\t\t'function writeResult(payload) {',\n\t\t'\tfs.mkdirSync(artifactDir, { recursive: true });',\n\t\t'\tfs.writeFileSync(resultPath, JSON.stringify(payload, null, 2));',\n\t\t'}',\n\t\t'',\n\t\t'function requestJson(url, payload) {',\n\t\t'\treturn new Promise((resolve, reject) => {',\n\t\t'\t\tconst body = JSON.stringify(payload || {});',\n\t\t'\t\tconst parsed = new URL(url);',\n\t\t'\t\tconst mod = parsed.protocol === \"https:\" ? https : http;',\n\t\t'\t\tconst req = mod.request(parsed, {',\n\t\t'\t\t\tmethod: \"POST\",',\n\t\t'\t\t\ttimeout: 20000,',\n\t\t'\t\t\theaders: {',\n\t\t'\t\t\t\t\"content-type\": \"application/json\",',\n\t\t'\t\t\t\t\"content-length\": Buffer.byteLength(body),',\n\t\t'\t\t\t\t\"origin\": clientUrl',\n\t\t'\t\t\t}',\n\t\t'\t\t}, (res) => {',\n\t\t'\t\t\tlet raw = \"\";',\n\t\t'\t\t\tres.setEncoding(\"utf8\");',\n\t\t'\t\t\tres.on(\"data\", (chunk) => { raw += chunk; });',\n\t\t'\t\t\tres.on(\"end\", () => {',\n\t\t'\t\t\t\tlet json = null;',\n\t\t'\t\t\t\ttry { json = raw ? JSON.parse(raw) : {}; }',\n\t\t'\t\t\t\tcatch (error) {',\n\t\t'\t\t\t\t\treject(new Error(`${url} returned non-JSON HTTP ${res.statusCode}: ${raw.slice(0, 300)}`));',\n\t\t'\t\t\t\t\treturn;',\n\t\t'\t\t\t\t}',\n\t\t'\t\t\t\tif (!res.statusCode || res.statusCode >= 400) {',\n\t\t'\t\t\t\t\treject(new Error(`${url} returned HTTP ${res.statusCode}: ${JSON.stringify(json).slice(0, 500)}`));',\n\t\t'\t\t\t\t\treturn;',\n\t\t'\t\t\t\t}',\n\t\t'\t\t\t\tresolve(json);',\n\t\t'\t\t\t});',\n\t\t'\t\t});',\n\t\t'\t\treq.on(\"timeout\", () => req.destroy(new Error(`${url} timed out`)));',\n\t\t'\t\treq.on(\"error\", reject);',\n\t\t'\t\treq.write(body);',\n\t\t'\t\treq.end();',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'function requestReady(url) {',\n\t\t'\treturn new Promise((resolve) => {',\n\t\t'\t\ttry {',\n\t\t'\t\t\tconst parsed = new URL(url);',\n\t\t'\t\t\tconst mod = parsed.protocol === \"https:\" ? https : http;',\n\t\t'\t\t\tconst req = mod.get(parsed, { timeout: 2500 }, (res) => {',\n\t\t'\t\t\t\tres.resume();',\n\t\t'\t\t\t\tresolve(Boolean(res.statusCode && res.statusCode < 500));',\n\t\t'\t\t\t});',\n\t\t'\t\t\treq.on(\"timeout\", () => req.destroy(new Error(\"timeout\")));',\n\t\t'\t\t\treq.on(\"error\", () => resolve(false));',\n\t\t'\t\t}',\n\t\t'\t\tcatch (error) {',\n\t\t'\t\t\tresolve(false);',\n\t\t'\t\t}',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'async function waitForHttpReady(url, label) {',\n\t\t'\tconst deadline = Date.now() + startupTimeoutMs;',\n\t\t'\twhile (Date.now() < deadline) {',\n\t\t'\t\tif (await requestReady(url)) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tawait delay(3000);',\n\t\t'\t}',\n\t\t'\tthrow new Error(`${label} did not become ready at ${url} within ${Math.round(startupTimeoutMs / 1000)}s`);',\n\t\t'}',\n\t\t'',\n\t\t'function requirePuppeteer() {',\n\t\t'\tconst candidates = [',\n\t\t'\t\tpath.join(projectRoot, \"server\", \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\tpath.join(projectRoot, \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\tpath.join(process.cwd(), \"server\", \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\tpath.join(process.cwd(), \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\t\"puppeteer\"',\n\t\t'\t];',\n\t\t'\tfor (const candidate of candidates) {',\n\t\t'\t\ttry { return require(candidate); }',\n\t\t'\t\tcatch (error) {}',\n\t\t'\t}',\n\t\t'\tthrow new Error(\"Unable to require puppeteer from project/server node_modules or global resolution\");',\n\t\t'}',\n\t\t'',\n\t\t'async function login() {',\n\t\t'\tif (!password) {',\n\t\t'\t\tthrow new Error(\"QA password is empty; source .resolveio-support-tools/env.sh or set RESOLVEIO_RUNNER_QA_PASSWORD before auth bootstrap\");',\n\t\t'\t}',\n\t\t'\tawait waitForHttpReady(serverUrl, \"QA server\");',\n\t\t'\tawait ensureLocalQaUser();',\n\t\t'\tlet loginJson = await requestJson(`${serverUrl}/login`, { username, password });',\n\t\t'\tif ((loginJson && loginJson.error) && /Invalid Username And Password|Too Many Attempts/i.test(String(loginJson.result || \"\"))) {',\n\t\t'\t\tconst repaired = await ensureLocalQaUser();',\n\t\t'\t\tif (repaired) {',\n\t\t'\t\t\tloginJson = await requestJson(`${serverUrl}/login`, { username, password });',\n\t\t'\t\t}',\n\t\t'\t}',\n\t\t'\tconst refreshToken = loginJson && loginJson.result && loginJson.result.token;',\n\t\t'\tif (loginJson.error || !refreshToken) {',\n\t\t'\t\tthrow new Error(`Login failed: ${JSON.stringify(loginJson).slice(0, 800)}`);',\n\t\t'\t}',\n\t\t'\tconst accessJson = await requestJson(`${serverUrl}/accessToken`, { refreshToken });',\n\t\t'\tconst accessToken = accessJson && accessJson.result && accessJson.result.token;',\n\t\t'\tconst user = accessJson && accessJson.result && accessJson.result.user;',\n\t\t'\tif (accessJson.error || !accessToken || !user) {',\n\t\t'\t\tthrow new Error(`Access token failed: ${JSON.stringify(accessJson).slice(0, 800)}`);',\n\t\t'\t}',\n\t\t'\treturn { refreshToken, accessToken, user: normalizeQaUserForBrowser(user) };',\n\t\t'}',\n\t\t'',\n\t\t'async function launchBrowser(puppeteer) {',\n\t\t'\tconst browserUrl = process.env.RESOLVEIO_RUNNER_QA_BROWSER_URL || process.env.RESOLVEIO_SUPPORT_QA_BROWSER_URL || \"\";',\n\t\t'\tif (browserUrl) {',\n\t\t'\t\treturn puppeteer.connect({ browserURL: browserUrl, protocolTimeout: 30000, defaultViewport: null });',\n\t\t'\t}',\n\t\t'\tconst launchOptions = {',\n\t\t'\t\theadless: true,',\n\t\t'\t\tdefaultViewport: { width: viewportWidth, height: viewportHeight },',\n\t\t'\t\targs: [\"--no-sandbox\", \"--disable-setuid-sandbox\", \"--disable-dev-shm-usage\", `--window-size=${viewportWidth},${viewportHeight}`]',\n\t\t'\t};',\n\t\t'\tif (process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN) {',\n\t\t'\t\tlaunchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN;',\n\t\t'\t}',\n\t\t'\treturn puppeteer.launch(launchOptions);',\n\t\t'}',\n\t\t'',\n\t\t'async function resetBrowserState(page) {',\n\t\t'\tawait waitForHttpReady(clientUrl, \"QA client\");',\n\t\t'\tawait page.goto(clientUrl, { waitUntil: \"domcontentloaded\", timeout: 45000 });',\n\t\t'\tawait page.evaluate(async () => {',\n\t\t'\t\ttry {',\n\t\t'\t\t\tconst registrations = await navigator.serviceWorker.getRegistrations();',\n\t\t'\t\t\tawait Promise.all(registrations.map((registration) => registration.unregister().catch(() => false)));',\n\t\t'\t\t} catch (error) {}',\n\t\t'\t\ttry {',\n\t\t'\t\t\tif (window.caches && window.caches.keys) {',\n\t\t'\t\t\t\tconst keys = await window.caches.keys();',\n\t\t'\t\t\t\tawait Promise.all(keys.map((key) => window.caches.delete(key).catch(() => false)));',\n\t\t'\t\t\t}',\n\t\t'\t\t} catch (error) {}',\n\t\t'\t\ttry {',\n\t\t'\t\t\tif (window.indexedDB && window.indexedDB.databases) {',\n\t\t'\t\t\t\tconst databases = await window.indexedDB.databases();',\n\t\t'\t\t\t\tawait Promise.all(databases.filter((database) => database && database.name).map((database) => new Promise((resolve) => {',\n\t\t'\t\t\t\t\tconst request = window.indexedDB.deleteDatabase(database.name);',\n\t\t'\t\t\t\t\trequest.onsuccess = () => resolve(true);',\n\t\t'\t\t\t\t\trequest.onerror = () => resolve(false);',\n\t\t'\t\t\t\t\trequest.onblocked = () => resolve(false);',\n\t\t'\t\t\t\t})));',\n\t\t'\t\t\t}',\n\t\t'\t\t} catch (error) {}',\n\t\t'\t\tlocalStorage.clear();',\n\t\t'\t\tsessionStorage.clear();',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'async function seedAuth(page, auth) {',\n\t\t'\tauth.user = normalizeQaUserForBrowser(auth.user);',\n\t\t'\tawait page.evaluate((payload) => {',\n\t\t'\t\tlocalStorage.setItem(\"refreshToken\", payload.refreshToken);',\n\t\t'\t\tlocalStorage.setItem(\"accessToken\", payload.accessToken);',\n\t\t'\t\tlocalStorage.setItem(\"user\", JSON.stringify(payload.user));',\n\t\t'\t\tlocalStorage.setItem(\"lastURL\", payload.lastURL);',\n\t\t'\t\tlocalStorage.setItem(\"resolveio.runnerQaAuthBootstrappedAt\", payload.bootstrappedAt);',\n\t\t'\t}, { ...auth, lastURL: targetRoute, bootstrappedAt: new Date().toISOString() });',\n\t\t'}',\n\t\t'',\n\t\t'async function patchBrowserQaUser(page) {',\n\t\t'\tawait page.evaluate((nextRoute) => {',\n\t\t'\t\tlet user = {};',\n\t\t'\t\ttry { user = JSON.parse(localStorage.getItem(\"user\") || \"{}\"); } catch (error) { user = {}; }',\n\t\t'\t\tconst settings = {',\n\t\t'\t\t\ttable_color: \"#3b3ee3\",',\n\t\t'\t\t\ttable_font_color: \"#ffffff\",',\n\t\t'\t\t\tsecondary_table_color: \"#87ceeb\",',\n\t\t'\t\t\tsecondary_table_font_color: \"#000000\",',\n\t\t'\t\t\ttertiary_table_color: \"#ff4500\",',\n\t\t'\t\t\ttertiary_table_font_color: \"#000000\",',\n\t\t'\t\t\tfont_size: 12,',\n\t\t'\t\t\tcollapsable_menu: true,',\n\t\t'\t\t\tentries_per_page: \"25\",',\n\t\t'\t\t\twarning_color: \"#ffc107\",',\n\t\t'\t\t\twarning_font_color: \"#000000\",',\n\t\t'\t\t\twarning_hover_color: \"#e0a800\",',\n\t\t'\t\t\tsuccess_color: \"#28a745\",',\n\t\t'\t\t\tsuccess_font_color: \"#ffffff\",',\n\t\t'\t\t\tsuccess_hover_color: \"#218838\",',\n\t\t'\t\t\tdanger_color: \"#dc3545\",',\n\t\t'\t\t\tdanger_font_color: \"#ffffff\",',\n\t\t'\t\t\tdanger_hover_color: \"#c82333\",',\n\t\t'\t\t\tinfo_color: \"#17a2b8\",',\n\t\t'\t\t\tinfo_font_color: \"#ffffff\",',\n\t\t'\t\t\tinfo_hover_color: \"#138496\",',\n\t\t'\t\t\tprimary_color: \"#007bff\",',\n\t\t'\t\t\tprimary_font_color: \"#ffffff\",',\n\t\t'\t\t\tprimary_hover_color: \"#0069d9\",',\n\t\t'\t\t\tsecondary_color: \"#868e96\",',\n\t\t'\t\t\tsecondary_font_color: \"#ffffff\",',\n\t\t'\t\t\tsecondary_hover_color: \"#5a6268\",',\n\t\t'\t\t\trouting_preference: \"\",',\n\t\t'\t\t\topening_route: nextRoute,',\n\t\t'\t\t\trio_select_search_mode: \"exact\"',\n\t\t'\t\t};',\n\t\t'\t\tuser = {',\n\t\t'\t\t\t...user,',\n\t\t'\t\t\tother: {',\n\t\t'\t\t\t\t...(user.other || {}),',\n\t\t'\t\t\t\tyards: Array.isArray(user.other && user.other.yards) ? user.other.yards : [],',\n\t\t'\t\t\t\ttour_completed: true,',\n\t\t'\t\t\t\ttook_tour: true,',\n\t\t'\t\t\t\tcore_tour_completed: true,',\n\t\t'\t\t\t\twelcome_tour_completed: true,',\n\t\t'\t\t\t\ttop_navigation_tour_completed: true,',\n\t\t'\t\t\t\tuser_settings_tour_completed: true',\n\t\t'\t\t\t},',\n\t\t'\t\t\tsettings: { ...settings, ...(user.settings || {}), opening_route: nextRoute }',\n\t\t'\t\t};',\n\t\t'\t\tlocalStorage.setItem(\"user\", JSON.stringify(user));',\n\t\t'\t\tlocalStorage.setItem(\"lastURL\", nextRoute);',\n\t\t'\t\tlocalStorage.setItem(\"resolveio.runnerQaPostLoginGateRepairedAt\", new Date().toISOString());',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'async function dismissVisibleTourOrSetupGate(page) {',\n\t\t'\treturn page.evaluate(() => {',\n\t\t'\t\tconst controls = Array.from(document.querySelectorAll(\"button, a, [role=\\'button\\']\"));',\n\t\t'\t\tconst control = controls.find((candidate) => {',\n\t\t'\t\t\tconst text = (candidate.textContent || \"\").replace(/\\\\s+/g, \" \").trim();',\n\t\t'\t\t\treturn /^(skip|finish|done|close)$/i.test(text);',\n\t\t'\t\t});',\n\t\t'\t\tif (control && typeof control.click === \"function\") {',\n\t\t'\t\t\tcontrol.click();',\n\t\t'\t\t\treturn true;',\n\t\t'\t\t}',\n\t\t'\t\treturn false;',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'function isPostLoginSetupOrTourGate(currentRoute, summary) {',\n\t\t'\tconst text = String(summary && summary.bodyTextSnippet || \"\");',\n\t\t'\treturn currentRoute === \"/user-settings/settings\" || /Top Navigation Step \\\\d+ of \\\\d+|\\\\bSkip\\\\b|User Settings/i.test(text);',\n\t\t'}',\n\t\t'',\n\t\t'async function repairPostLoginSetupOrTourGate(page, expectedRoute) {',\n\t\t'\tif (process.env.RESOLVEIO_RUNNER_QA_DISABLE_POST_LOGIN_ROUTE_REPAIR === \"true\" || process.env.RESOLVEIO_SUPPORT_QA_DISABLE_POST_LOGIN_ROUTE_REPAIR === \"true\") {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'\tconst current = normalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\tconst summary = await pageSummary(page).catch(() => ({}));',\n\t\t'\tif (!isPostLoginSetupOrTourGate(current, summary)) {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'\tawait ensureLocalQaUser().catch(() => false);',\n\t\t'\tawait refreshAuthAndSeed(page).catch(() => undefined);',\n\t\t'\tawait dismissVisibleTourOrSetupGate(page).catch(() => false);',\n\t\t'\tawait patchBrowserQaUser(page);',\n\t\t'\tawait page.goto(`${clientUrl}${expectedRoute}`, { waitUntil: \"domcontentloaded\", timeout: 60000 });',\n\t\t'\tawait delay(1500);',\n\t\t'\tawait dismissVisibleTourOrSetupGate(page).catch(() => false);',\n\t\t'\tawait patchBrowserQaUser(page);',\n\t\t'\tawait delay(500);',\n\t\t'\tconst repairedCurrent = normalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\treturn repairedCurrent === expectedRoute;',\n\t\t'}',\n\t\t'',\n\t\t'function delay(ms) {',\n\t\t'\treturn new Promise((resolve) => setTimeout(resolve, ms));',\n\t\t'}',\n\t\t'',\n\t\t'function normalizeRoutePath(value) {',\n\t\t'\ttry {',\n\t\t'\t\t\tconst parsed = new URL(value, clientUrl);',\n\t\t'\t\t\tlet pathname = parsed.pathname || \"/\";',\n\t\t'\t\t\tpathname = pathname.replace(/\\\\/+$/, \"\") || \"/\";',\n\t\t'\t\t\treturn pathname;',\n\t\t'\t}',\n\t\t'\tcatch (error) {',\n\t\t'\t\t\treturn String(value || \"/\").split(\"?\")[0].replace(/\\\\/+$/, \"\") || \"/\";',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'function isGenericAuthTargetRoute(routePath) {',\n\t\t'\treturn [\"/\", \"/screen\", \"/home\"].includes(normalizeRoutePath(routePath));',\n\t\t'}',\n\t\t'',\n\t\t'function isAuthenticatedNonSetupRoute(summary) {',\n\t\t'\tconst current = normalizeRoutePath(summary && summary.url || \"\");',\n\t\t'\treturn !!summary',\n\t\t'\t\t&& !!summary.hasRefreshToken',\n\t\t'\t\t&& !!summary.hasAccessToken',\n\t\t'\t\t&& !!summary.hasUser',\n\t\t'\t\t&& !summary.hasLoginText',\n\t\t'\t\t&& !summary.hasOfflineModeText',\n\t\t'\t\t&& current !== \"/user-settings/settings\";',\n\t\t'}',\n\t\t'',\n\t\t'async function assertTargetRoute(page) {',\n\t\t'\tconst expected = normalizeRoutePath(targetRoute);',\n\t\t'\tif (expected === \"/\") {',\n\t\t'\t\treturn;',\n\t\t'\t}',\n\t\t'\tconst current = normalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\tif (current !== expected) {',\n\t\t'\t\tconst repaired = await repairPostLoginSetupOrTourGate(page, expected);',\n\t\t'\t\tif (repaired) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tconst summary = await pageSummary(page).catch(() => ({}));',\n\t\t'\t\tif (isGenericAuthTargetRoute(expected) && isAuthenticatedNonSetupRoute(summary)) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tthrow new Error(`QA auth bootstrap reached ${current}, not requested target route ${expected}. This is a route blocker; do not continue browser QA until the runner/app can reach the requested screen. Page summary: ${JSON.stringify(summary).slice(0, 1200)}`);',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'async function waitForStableTargetRoute(page) {',\n\t\t'\tconst expected = normalizeRoutePath(targetRoute);',\n\t\t'\tif (isGenericAuthTargetRoute(expected)) {',\n\t\t'\t\treturn;',\n\t\t'\t}',\n\t\t'\tconst deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_STABILITY_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_STABILITY_TIMEOUT_MS || 7000);',\n\t\t'\tlet stableSince = 0;',\n\t\t'\tlet lastCurrent = \"\";',\n\t\t'\twhile (Date.now() < deadline) {',\n\t\t'\t\tconst current = normalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\t\tlastCurrent = current;',\n\t\t'\t\tif (current === expected) {',\n\t\t'\t\t\tstableSince = stableSince || Date.now();',\n\t\t'\t\t\tif (Date.now() - stableSince >= Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_STABLE_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_STABLE_MS || 2500)) {',\n\t\t'\t\t\t\treturn;',\n\t\t'\t\t\t}',\n\t\t'\t\t}',\n\t\t'\t\telse {',\n\t\t'\t\t\tstableSince = 0;',\n\t\t'\t\t}',\n\t\t'\t\tawait delay(250);',\n\t\t'\t}',\n\t\t'\tconst summary = await pageSummary(page).catch(() => ({}));',\n\t\t'\tthrow new Error(`QA auth bootstrap route was not stable on requested target ${expected}; last route was ${lastCurrent || \"unknown\"}. This is a route blocker; do not continue browser QA until the runner/app can remain on the requested screen. Page summary: ${JSON.stringify(summary).slice(0, 1200)}`);',\n\t\t'}',\n\t\t'',\n\t\t'async function dismissNavigationOverlays(page) {',\n\t\t'\ttry {',\n\t\t'\t\tawait page.keyboard.press(\"Escape\");',\n\t\t'\t} catch (error) {}',\n\t\t'\ttry {',\n\t\t'\t\tawait page.mouse.move(16, Math.max(120, viewportHeight - 24));',\n\t\t'\t} catch (error) {}',\n\t\t'\ttry {',\n\t\t'\t\tawait page.evaluate(() => {',\n\t\t'\t\t\tif (document.activeElement && typeof document.activeElement.blur === \"function\") document.activeElement.blur();',\n\t\t'\t\t\tdocument.body && document.body.dispatchEvent(new MouseEvent(\"mousemove\", { bubbles: true, clientX: 16, clientY: Math.max(120, window.innerHeight - 24) }));',\n\t\t'\t\t});',\n\t\t'\t} catch (error) {}',\n\t\t'\tawait delay(350);',\n\t\t'}',\n\t\t'',\n\t\t'async function assertSummaryOnTargetRoute(summary) {',\n\t\t'\tconst expected = normalizeRoutePath(targetRoute);',\n\t\t'\tif (expected === \"/\") {',\n\t\t'\t\treturn;',\n\t\t'\t}',\n\t\t'\tconst current = normalizeRoutePath(summary && summary.url || \"\");',\n\t\t'\tif (current !== expected) {',\n\t\t'\t\tif (isGenericAuthTargetRoute(expected) && isAuthenticatedNonSetupRoute(summary)) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tthrow new Error(`QA auth bootstrap final summary is on ${current}, not requested target route ${expected}. This is a route blocker; refusing to write a false pass. Page summary: ${JSON.stringify(summary).slice(0, 1200)}`);',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'async function logoutExistingBrowserSession(page) {',\n\t\t'\ttry {',\n\t\t'\t\tawait page.goto(clientUrl, { waitUntil: \"domcontentloaded\", timeout: 45000 });',\n\t\t'\t\tawait delay(500);',\n\t\t'\t\tawait page.evaluate(() => {',\n\t\t'\t\t\tconst controls = Array.from(document.querySelectorAll(\"button, a, [role=\\'button\\']\"));',\n\t\t'\t\t\tconst logoutControl = controls.find((control) => /(^|\\\\s)logout(\\\\s|$)/i.test((control.textContent || \"\").trim()));',\n\t\t'\t\t\tif (logoutControl && typeof logoutControl.click === \"function\") {',\n\t\t'\t\t\t\tlogoutControl.click();',\n\t\t'\t\t\t\treturn true;',\n\t\t'\t\t\t}',\n\t\t'\t\t\treturn false;',\n\t\t'\t\t});',\n\t\t'\t\tawait delay(1000);',\n\t\t'\t}',\n\t\t'\tcatch (error) {}',\n\t\t'}',\n\t\t'',\n\t\t'async function waitForAuthenticatedApp(page) {',\n\t\t'\tconst url = `${clientUrl}${targetRoute}`;',\n\t\t'\tawait page.goto(url, { waitUntil: \"domcontentloaded\", timeout: 60000 });',\n\t\t'\tawait page.waitForFunction(() => {',\n\t\t'\t\tconst text = (document.body && document.body.innerText || \"\").replace(/\\\\s+/g, \" \").trim();',\n\t\t'\t\tconst hasTokens = !!localStorage.getItem(\"refreshToken\") && !!localStorage.getItem(\"accessToken\") && !!localStorage.getItem(\"user\");',\n\t\t'\t\tconst hasLogin = /Employee\\\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(text);',\n\t\t'\t\tconst hasOffline = text.includes(\"*** OFFLINE MODE ***\");',\n\t\t'\t\treturn hasTokens && !hasLogin && !hasOffline && text.length > 40;',\n\t\t'\t}, { timeout: Number(process.env.RESOLVEIO_RUNNER_QA_AUTH_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_AUTH_TIMEOUT_MS || 60000) });',\n\t\t'\tawait delay(1000);',\n\t\t'\tawait assertTargetRoute(page);',\n\t\t'\tawait waitForStableTargetRoute(page);',\n\t\t'\tawait dismissNavigationOverlays(page);',\n\t\t'}',\n\t\t'',\n\t\t'async function pageSummary(page) {',\n\t\t'\treturn page.evaluate(() => {',\n\t\t'\t\tconst bodyText = (document.body && document.body.innerText || \"\").replace(/\\\\s+/g, \" \").trim();',\n\t\t'\t\treturn {',\n\t\t'\t\t\turl: location.href,',\n\t\t'\t\t\ttitle: document.title,',\n\t\t'\t\t\thasAngularDebug: !!window.ng,',\n\t\t'\t\t\thasRefreshToken: !!localStorage.getItem(\"refreshToken\"),',\n\t\t'\t\t\thasAccessToken: !!localStorage.getItem(\"accessToken\"),',\n\t\t'\t\t\thasUser: !!localStorage.getItem(\"user\"),',\n\t\t'\t\t\thasOfflineModeText: bodyText.includes(\"*** OFFLINE MODE ***\"),',\n\t\t'\t\t\thasLoginText: /Employee\\\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(bodyText),',\n\t\t'\t\t\tbodyTextSnippet: bodyText.slice(0, 800),',\n\t\t'\t\t\tlocalStorageKeys: Object.keys(localStorage).sort()',\n\t\t'\t\t};',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'(async () => {',\n\t\t'\tfs.mkdirSync(artifactDir, { recursive: true });',\n\t\t'\tawait waitForHttpReady(clientUrl, \"QA client\");',\n\t\t'\tawait waitForHttpReady(serverUrl, \"QA server\");',\n\t\t'\tconst puppeteer = requirePuppeteer();',\n\t\t'\tconst browser = await launchBrowser(puppeteer);',\n\t\t'\tlet page;',\n\t\t'\ttry {',\n\t\t'\t\tpage = await browser.newPage();',\n\t\t'\t\tawait page.setViewport({ width: viewportWidth, height: viewportHeight });',\n\t\t'\t\tpage.on(\"console\", (msg) => {',\n\t\t'\t\t\tconst text = msg.text();',\n\t\t'\t\t\tif ([\"error\", \"warning\"].includes(msg.type()) || /error/i.test(text)) {',\n\t\t'\t\t\t\tconsole.log(\"[browser console]\", msg.type(), text);',\n\t\t'\t\t\t}',\n\t\t'\t\t});',\n\t\t'\t\tpage.on(\"pageerror\", (error) => console.log(\"[pageerror]\", error.message));',\n\t\t'\t\tawait logoutExistingBrowserSession(page);',\n\t\t'\t\tawait resetBrowserState(page);',\n\t\t'\t\tconst auth = await login();',\n\t\t'\t\tawait seedAuth(page, auth);',\n\t\t'\t\tawait waitForAuthenticatedApp(page);',\n\t\t'\t\tconst finalPage = await pageSummary(page);',\n\t\t'\t\tawait assertSummaryOnTargetRoute(finalPage);',\n\t\t'\t\tawait page.screenshot({ path: readyScreenshotPath, type: \"jpeg\", quality: 82, fullPage: false });',\n\t\t'\t\tconst summary = {',\n\t\t'\t\t\tstatus: \"pass\",',\n\t\t'\t\t\tclientUrl,',\n\t\t'\t\t\tserverUrl,',\n\t\t'\t\t\ttargetRoute,',\n\t\t'\t\t\tscreenshot: readyScreenshotPath,',\n\t\t'\t\t\tuser: { _id: auth.user && auth.user._id, username: auth.user && auth.user.username, fullname: auth.user && auth.user.fullname },',\n\t\t'\t\t\tpage: finalPage',\n\t\t'\t\t};',\n\t\t'\t\twriteResult(summary);',\n\t\t'\t\tconsole.log(JSON.stringify(summary, null, 2));',\n\t\t'\t}',\n\t\t'\tcatch (error) {',\n\t\t'\t\tlet summary = { status: \"fail\", clientUrl, serverUrl, targetRoute, screenshot: failureScreenshotPath, error: error && error.stack || String(error) };',\n\t\t'\t\ttry {',\n\t\t'\t\t\tif (page) {',\n\t\t'\t\t\t\tawait page.screenshot({ path: failureScreenshotPath, type: \"jpeg\", quality: 82, fullPage: false });',\n\t\t'\t\t\t\tsummary.page = await pageSummary(page);',\n\t\t'\t\t\t}',\n\t\t'\t\t} catch (screenshotError) {',\n\t\t'\t\t\tsummary.screenshotError = screenshotError && screenshotError.stack || String(screenshotError);',\n\t\t'\t\t}',\n\t\t'\t\twriteResult(summary);',\n\t\t'\t\tconsole.error(JSON.stringify(summary, null, 2));',\n\t\t'\t\tprocess.exitCode = 1;',\n\t\t'\t}',\n\t\t'\tfinally {',\n\t\t'\t\tif (browser && typeof browser.close === \"function\") {',\n\t\t'\t\t\tawait browser.close().catch(() => undefined);',\n\t\t'\t\t}',\n\t\t'\t\tprocess.exit(process.exitCode || 0);',\n\t\t'\t}',\n\t\t'})();',\n\t\t''\n\t].join('\\n');\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/util/ai-runner-qa-auth.ts"],"names":[],"mappings":"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","file":"ai-runner-qa-auth.js","sourcesContent":["export interface ResolveIORunnerQaAuthBootstrapScriptOptions {\n\tdefaultUsername?: string;\n\tdefaultPassword?: string;\n}\n\nexport function buildResolveIORunnerQaAuthBootstrapScript(options: ResolveIORunnerQaAuthBootstrapScriptOptions = {}): string {\n\tconst defaultUsername = options.defaultUsername || 'admin';\n\tconst defaultPassword = options.defaultPassword || '';\n\treturn [\n\t\t'#!/usr/bin/env node',\n\t\t\"'use strict';\",\n\t\t'',\n\t\t'const fs = require(\"fs\");',\n\t\t'const crypto = require(\"crypto\");',\n\t\t'const http = require(\"http\");',\n\t\t'const https = require(\"https\");',\n\t\t'const path = require(\"path\");',\n\t\t'',\n\t\t'const projectRoot = path.resolve(process.argv[2] || process.cwd());',\n\t\t'const routeArg = process.argv[3] || process.env.RESOLVEIO_RUNNER_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_LAST_URL || \"/\";',\n\t\t'const targetRoute = routeArg.startsWith(\"/\") ? routeArg : `/${routeArg}`;',\n\t\t'const clientUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_CLIENT_URL || process.env.RESOLVEIO_SUPPORT_QA_CLIENT_URL || `http://localhost:${process.env.RESOLVEIO_SUPPORT_QA_CLIENT_PORT || \"4200\"}`);',\n\t\t'const serverUrl = stripTrailingSlash(process.env.RESOLVEIO_RUNNER_QA_SERVER_URL || process.env.RESOLVEIO_SUPPORT_QA_SERVER_URL || \"http://localhost:8080\");',\n\t\t`const username = process.env.RESOLVEIO_RUNNER_QA_USERNAME || process.env.RESOLVEIO_SUPPORT_QA_USERNAME || ${JSON.stringify(defaultUsername)};`,\n\t\t`const password = process.env.RESOLVEIO_RUNNER_QA_PASSWORD || process.env.RESOLVEIO_SUPPORT_QA_PASSWORD || ${JSON.stringify(defaultPassword)};`,\n\t\t'const artifactDir = path.resolve(process.env.RESOLVEIO_RUNNER_QA_ARTIFACT_DIR || process.env.RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR || path.join(projectRoot, \"qa-artifacts\"));',\n\t\t'const viewportWidth = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_WIDTH || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_WIDTH || 1920);',\n\t\t'const viewportHeight = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_HEIGHT || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_HEIGHT || 1080);',\n\t\t'const startupTimeoutMs = Math.max(1000, Number(process.env.RESOLVEIO_RUNNER_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS || process.env.RESOLVEIO_SUPPORT_QA_ANGULAR_STARTUP_TIMEOUT_SECONDS || 900) * 1000);',\n\t\t'const resultPath = path.join(artifactDir, \"auth-bootstrap-result.json\");',\n\t\t'const storageStatePath = path.join(artifactDir, \"auth-bootstrap-storage-state.json\");',\n\t\t'const readyScreenshotPath = path.join(artifactDir, \"auth-bootstrap-ready.jpg\");',\n\t\t'const failureScreenshotPath = path.join(artifactDir, \"auth-bootstrap-failed.jpg\");',\n\t\t'',\n\t\t'function stripTrailingSlash(value) {',\n\t\t'\treturn String(value || \"\").replace(/\\\\/+$/, \"\");',\n\t\t'}',\n\t\t'',\n\t\t'function isLocalhostUrl(value) {',\n\t\t'\ttry {',\n\t\t'\t\tconst parsed = new URL(value);',\n\t\t'\t\treturn [\"localhost\", \"127.0.0.1\", \"::1\"].includes(parsed.hostname);',\n\t\t'\t}',\n\t\t'\tcatch (error) {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'function resolveLocalMongoUrl() {',\n\t\t'\tconst localQaMongoUrl = process.env.RESOLVEIO_RUNNER_QA_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_MONGO_URL || `mongodb://127.0.0.1:${process.env.RESOLVEIO_SUPPORT_QA_MONGO_PORT || \"3001\"}/resolveio?directConnection=true`;',\n\t\t'\tif (isLocalhostUrl(serverUrl)) {',\n\t\t'\t\treturn localQaMongoUrl;',\n\t\t'\t}',\n\t\t'\treturn process.env.MONGO_URL || localQaMongoUrl;',\n\t\t'}',\n\t\t'',\n\t\t'function requireMongoClient() {',\n\t\t'\tconst candidates = [',\n\t\t'\t\tpath.join(projectRoot, \"server\", \"node_modules\", \"mongodb\"),',\n\t\t'\t\tpath.join(projectRoot, \"node_modules\", \"mongodb\"),',\n\t\t'\t\tpath.join(process.cwd(), \"server\", \"node_modules\", \"mongodb\"),',\n\t\t'\t\tpath.join(process.cwd(), \"node_modules\", \"mongodb\"),',\n\t\t'\t\t\"mongodb\"',\n\t\t'\t];',\n\t\t'\tfor (const candidate of candidates) {',\n\t\t'\t\ttry { return require(candidate).MongoClient; }',\n\t\t'\t\tcatch (error) {}',\n\t\t'\t}',\n\t\t'\tthrow new Error(\"Unable to require mongodb from project/server node_modules or global resolution\");',\n\t\t'}',\n\t\t'',\n\t\t'function buildDefaultQaUserSettings() {',\n\t\t'\treturn {',\n\t\t'\t\ttable_color: \"#3b3ee3\",',\n\t\t'\t\ttable_font_color: \"#ffffff\",',\n\t\t'\t\tsecondary_table_color: \"#87ceeb\",',\n\t\t'\t\tsecondary_table_font_color: \"#000000\",',\n\t\t'\t\ttertiary_table_color: \"#ff4500\",',\n\t\t'\t\ttertiary_table_font_color: \"#000000\",',\n\t\t'\t\tfont_size: 12,',\n\t\t'\t\tcollapsable_menu: true,',\n\t\t'\t\tentries_per_page: \"25\",',\n\t\t'\t\twarning_color: \"#ffc107\",',\n\t\t'\t\twarning_font_color: \"#000000\",',\n\t\t'\t\twarning_hover_color: \"#e0a800\",',\n\t\t'\t\tsuccess_color: \"#28a745\",',\n\t\t'\t\tsuccess_font_color: \"#ffffff\",',\n\t\t'\t\tsuccess_hover_color: \"#218838\",',\n\t\t'\t\tdanger_color: \"#dc3545\",',\n\t\t'\t\tdanger_font_color: \"#ffffff\",',\n\t\t'\t\tdanger_hover_color: \"#c82333\",',\n\t\t'\t\tinfo_color: \"#17a2b8\",',\n\t\t'\t\tinfo_font_color: \"#ffffff\",',\n\t\t'\t\tinfo_hover_color: \"#138496\",',\n\t\t'\t\tprimary_color: \"#007bff\",',\n\t\t'\t\tprimary_font_color: \"#ffffff\",',\n\t\t'\t\tprimary_hover_color: \"#0069d9\",',\n\t\t'\t\tsecondary_color: \"#868e96\",',\n\t\t'\t\tsecondary_font_color: \"#ffffff\",',\n\t\t'\t\tsecondary_hover_color: \"#5a6268\",',\n\t\t'\t\trouting_preference: \"\",',\n\t\t'\t\topening_route: targetRoute,',\n\t\t'\t\trio_select_search_mode: \"exact\"',\n\t\t'\t};',\n\t\t'}',\n\t\t'',\n\t\t'function normalizeQaUserForBrowser(user) {',\n\t\t'\tconst normalized = { ...(user || {}) };',\n\t\t'\tnormalized.other = {',\n\t\t'\t\t...(normalized.other || {}),',\n\t\t'\t\tyards: Array.isArray(normalized.other && normalized.other.yards) ? normalized.other.yards : [],',\n\t\t'\t\ttour_completed: true,',\n\t\t'\t\ttook_tour: true,',\n\t\t'\t\tcore_tour_completed: true,',\n\t\t'\t\twelcome_tour_completed: true,',\n\t\t'\t\ttop_navigation_tour_completed: true,',\n\t\t'\t\tuser_settings_tour_completed: true',\n\t\t'\t};',\n\t\t'\tnormalized.settings = {',\n\t\t'\t\t...buildDefaultQaUserSettings(),',\n\t\t'\t\t...(normalized.settings || {}),',\n\t\t'\t\topening_route: targetRoute',\n\t\t'\t};',\n\t\t'\treturn normalized;',\n\t\t'}',\n\t\t'',\n\t\t'async function ensureLocalQaUser() {',\n\t\t'\tif (!isLocalhostUrl(serverUrl) || process.env.RESOLVEIO_RUNNER_QA_DISABLE_LOCAL_USER_REPAIR === \"true\" || process.env.RESOLVEIO_SUPPORT_QA_DISABLE_LOCAL_USER_REPAIR === \"true\") {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'\tconst MongoClient = requireMongoClient();',\n\t\t'\tconst client = new MongoClient(resolveLocalMongoUrl());',\n\t\t'\tawait client.connect();',\n\t\t'\ttry {',\n\t\t'\t\tconst db = client.db();',\n\t\t'\t\tconst users = db.collection(\"users\");',\n\t\t'\t\tconst now = new Date();',\n\t\t'\t\tconst salt = crypto.randomBytes(32).toString(\"hex\");',\n\t\t'\t\tconst hash = crypto.pbkdf2Sync(password, salt, 25000, 512, \"sha256\").toString(\"hex\");',\n\t\t'\t\tconst existing = await users.findOne({ $or: [{ username }, { email: username }] }, { projection: { _id: 1 } });',\n\t\t'\t\tconst defaultSettings = buildDefaultQaUserSettings();',\n\t\t'\t\tconst defaultOther = { yards: [], date_picker_day_start: \"S\", tour_completed: true, took_tour: true, core_tour_completed: true, welcome_tour_completed: true, top_navigation_tour_completed: true, user_settings_tour_completed: true, tour_completed_at: now.toISOString() };',\n\t\t'\t\tconst baseUser = {',\n\t\t'\t\t\t__v: 0,',\n\t\t'\t\t\tusername,',\n\t\t'\t\t\temail: username.includes(\"@\") ? username : \"dev@resolveio.com\",',\n\t\t'\t\t\tfullname: \"QA Admin\",',\n\t\t'\t\t\troles: { super_admin: true, approvals: [], groups: [], notifications: [], miscs: [] },',\n\t\t'\t\t\tactive: true,',\n\t\t'\t\t\tother: defaultOther,',\n\t\t'\t\t\tsettings: defaultSettings,',\n\t\t'\t\t\tphonenumber: \"\",',\n\t\t'\t\t\treadonly: false,',\n\t\t'\t\t\tis_customer: false,',\n\t\t'\t\t\thash,',\n\t\t'\t\t\tsalt,',\n\t\t'\t\t\tattempts: 0,',\n\t\t'\t\t\tservices: {},',\n\t\t'\t\t\tupdatedAt: now',\n\t\t'\t\t};',\n\t\t'\t\tconst qaUserFilter = {',\n\t\t'\t\t\t$or: [',\n\t\t'\t\t\t\t{ username },',\n\t\t'\t\t\t\t{ email: username },',\n\t\t'\t\t\t\t{ username: \"admin\" },',\n\t\t'\t\t\t\t{ email: \"admin\" },',\n\t\t'\t\t\t\t{ email: \"dev@resolveio.com\" }',\n\t\t'\t\t\t]',\n\t\t'\t\t};',\n\t\t'\t\tawait users.updateMany(qaUserFilter, {',\n\t\t'\t\t\t$set: {',\n\t\t'\t\t\t\tactive: true,',\n\t\t'\t\t\t\troles: baseUser.roles,',\n\t\t'\t\t\t\tother: defaultOther,',\n\t\t'\t\t\t\tsettings: defaultSettings,',\n\t\t'\t\t\t\thash,',\n\t\t'\t\t\t\tsalt,',\n\t\t'\t\t\t\tattempts: 0,',\n\t\t'\t\t\t\treadonly: false,',\n\t\t'\t\t\t\tis_customer: false,',\n\t\t'\t\t\t\tupdatedAt: now',\n\t\t'\t\t\t}',\n\t\t'\t\t});',\n\t\t'\t\tif (existing && existing._id) {',\n\t\t'\t\t\tawait users.updateOne({ _id: existing._id }, { $set: baseUser });',\n\t\t'\t\t}',\n\t\t'\t\telse {',\n\t\t'\t\t\tawait users.insertOne({ _id: crypto.randomBytes(12).toString(\"hex\"), createdAt: now, ...baseUser });',\n\t\t'\t\t}',\n\t\t'\t\tconst repaired = await users.findOne(qaUserFilter, { projection: { _id: 1 } });',\n\t\t'\t\tif (repaired && repaired._id) {',\n\t\t'\t\t\tawait users.updateOne({ _id: repaired._id }, {',\n\t\t'\t\t\t\t$set: {',\n\t\t'\t\t\t\t\tactive: true,',\n\t\t'\t\t\t\t\troles: baseUser.roles,',\n\t\t'\t\t\t\t\tother: defaultOther,',\n\t\t'\t\t\t\t\tsettings: defaultSettings,',\n\t\t'\t\t\t\t\thash,',\n\t\t'\t\t\t\t\tsalt,',\n\t\t'\t\t\t\t\tattempts: 0,',\n\t\t'\t\t\t\t\treadonly: false,',\n\t\t'\t\t\t\t\tis_customer: false,',\n\t\t'\t\t\t\t\tupdatedAt: now',\n\t\t'\t\t\t\t}',\n\t\t'\t\t\t});',\n\t\t'\t\t}',\n\t\t'\t\treturn true;',\n\t\t'\t}',\n\t\t'\tfinally {',\n\t\t'\t\tawait client.close().catch(() => undefined);',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'async function refreshAuthAndSeed(page) {',\n\t\t'\tconst auth = await login();',\n\t\t'\tawait seedAuth(page, auth);',\n\t\t'\treturn auth;',\n\t\t'}',\n\t\t'',\n\t\t'function writeResult(payload) {',\n\t\t'\tfs.mkdirSync(artifactDir, { recursive: true });',\n\t\t'\tfs.writeFileSync(resultPath, JSON.stringify(payload, null, 2));',\n\t\t'}',\n\t\t'',\n\t\t'function writeAuthStorageState(auth) {',\n\t\t'\tconst normalizedUser = normalizeQaUserForBrowser(auth && auth.user);',\n\t\t'\tconst bootstrappedAt = new Date().toISOString();',\n\t\t'\tconst localStorage = {',\n\t\t'\t\trefreshToken: auth && auth.refreshToken,',\n\t\t'\t\taccessToken: auth && auth.accessToken,',\n\t\t'\t\tuser: JSON.stringify(normalizedUser),',\n\t\t'\t\tlastURL: targetRoute,',\n\t\t'\t\t\"resolveio.runnerQaAuthBootstrappedAt\": bootstrappedAt',\n\t\t'\t};',\n\t\t'\tconst payload = {',\n\t\t'\t\tstatus: \"pass\",',\n\t\t'\t\tclientUrl,',\n\t\t'\t\tserverUrl,',\n\t\t'\t\ttargetRoute,',\n\t\t'\t\tcreatedAt: bootstrappedAt,',\n\t\t'\t\trefreshToken: auth && auth.refreshToken,',\n\t\t'\t\taccessToken: auth && auth.accessToken,',\n\t\t'\t\tuser: normalizedUser,',\n\t\t'\t\tlocalStorage,',\n\t\t'\t\tcookies: [],',\n\t\t'\t\torigins: [{',\n\t\t'\t\t\torigin: clientUrl,',\n\t\t'\t\t\tlocalStorage: Object.entries(localStorage).map(([name, value]) => ({ name, value: String(value == null ? \"\" : value) }))',\n\t\t'\t\t}]',\n\t\t'\t};',\n\t\t'\tfs.mkdirSync(artifactDir, { recursive: true });',\n\t\t'\tfs.writeFileSync(storageStatePath, JSON.stringify(payload, null, 2));',\n\t\t'\treturn payload;',\n\t\t'}',\n\t\t'',\n\t\t'function requestJson(url, payload) {',\n\t\t'\treturn new Promise((resolve, reject) => {',\n\t\t'\t\tconst body = JSON.stringify(payload || {});',\n\t\t'\t\tconst parsed = new URL(url);',\n\t\t'\t\tconst mod = parsed.protocol === \"https:\" ? https : http;',\n\t\t'\t\tconst req = mod.request(parsed, {',\n\t\t'\t\t\tmethod: \"POST\",',\n\t\t'\t\t\ttimeout: 20000,',\n\t\t'\t\t\theaders: {',\n\t\t'\t\t\t\t\"content-type\": \"application/json\",',\n\t\t'\t\t\t\t\"content-length\": Buffer.byteLength(body),',\n\t\t'\t\t\t\t\"origin\": clientUrl',\n\t\t'\t\t\t}',\n\t\t'\t\t}, (res) => {',\n\t\t'\t\t\tlet raw = \"\";',\n\t\t'\t\t\tres.setEncoding(\"utf8\");',\n\t\t'\t\t\tres.on(\"data\", (chunk) => { raw += chunk; });',\n\t\t'\t\t\tres.on(\"end\", () => {',\n\t\t'\t\t\t\tlet json = null;',\n\t\t'\t\t\t\ttry { json = raw ? JSON.parse(raw) : {}; }',\n\t\t'\t\t\t\tcatch (error) {',\n\t\t'\t\t\t\t\treject(new Error(`${url} returned non-JSON HTTP ${res.statusCode}: ${raw.slice(0, 300)}`));',\n\t\t'\t\t\t\t\treturn;',\n\t\t'\t\t\t\t}',\n\t\t'\t\t\t\tif (!res.statusCode || res.statusCode >= 400) {',\n\t\t'\t\t\t\t\treject(new Error(`${url} returned HTTP ${res.statusCode}: ${JSON.stringify(json).slice(0, 500)}`));',\n\t\t'\t\t\t\t\treturn;',\n\t\t'\t\t\t\t}',\n\t\t'\t\t\t\tresolve(json);',\n\t\t'\t\t\t});',\n\t\t'\t\t});',\n\t\t'\t\treq.on(\"timeout\", () => req.destroy(new Error(`${url} timed out`)));',\n\t\t'\t\treq.on(\"error\", reject);',\n\t\t'\t\treq.write(body);',\n\t\t'\t\treq.end();',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'function requestReady(url) {',\n\t\t'\treturn new Promise((resolve) => {',\n\t\t'\t\ttry {',\n\t\t'\t\t\tconst parsed = new URL(url);',\n\t\t'\t\t\tconst mod = parsed.protocol === \"https:\" ? https : http;',\n\t\t'\t\t\tconst req = mod.get(parsed, { timeout: 2500 }, (res) => {',\n\t\t'\t\t\t\tres.resume();',\n\t\t'\t\t\t\tresolve(Boolean(res.statusCode && res.statusCode < 500));',\n\t\t'\t\t\t});',\n\t\t'\t\t\treq.on(\"timeout\", () => req.destroy(new Error(\"timeout\")));',\n\t\t'\t\t\treq.on(\"error\", () => resolve(false));',\n\t\t'\t\t}',\n\t\t'\t\tcatch (error) {',\n\t\t'\t\t\tresolve(false);',\n\t\t'\t\t}',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'async function waitForHttpReady(url, label) {',\n\t\t'\tconst deadline = Date.now() + startupTimeoutMs;',\n\t\t'\twhile (Date.now() < deadline) {',\n\t\t'\t\tif (await requestReady(url)) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tawait delay(3000);',\n\t\t'\t}',\n\t\t'\tthrow new Error(`${label} did not become ready at ${url} within ${Math.round(startupTimeoutMs / 1000)}s`);',\n\t\t'}',\n\t\t'',\n\t\t'function requirePuppeteer() {',\n\t\t'\tconst candidates = [',\n\t\t'\t\tpath.join(projectRoot, \"server\", \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\tpath.join(projectRoot, \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\tpath.join(process.cwd(), \"server\", \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\tpath.join(process.cwd(), \"node_modules\", \"puppeteer\", \"lib\", \"cjs\", \"puppeteer\", \"puppeteer.js\"),',\n\t\t'\t\t\"puppeteer\"',\n\t\t'\t];',\n\t\t'\tfor (const candidate of candidates) {',\n\t\t'\t\ttry { return require(candidate); }',\n\t\t'\t\tcatch (error) {}',\n\t\t'\t}',\n\t\t'\tthrow new Error(\"Unable to require puppeteer from project/server node_modules or global resolution\");',\n\t\t'}',\n\t\t'',\n\t\t'async function login() {',\n\t\t'\tif (!password) {',\n\t\t'\t\tthrow new Error(\"QA password is empty; source .resolveio-support-tools/env.sh or set RESOLVEIO_RUNNER_QA_PASSWORD before auth bootstrap\");',\n\t\t'\t}',\n\t\t'\tawait waitForHttpReady(serverUrl, \"QA server\");',\n\t\t'\tawait ensureLocalQaUser();',\n\t\t'\tlet loginJson = await requestJson(`${serverUrl}/login`, { username, password });',\n\t\t'\tif ((loginJson && loginJson.error) && /Invalid Username And Password|Too Many Attempts/i.test(String(loginJson.result || \"\"))) {',\n\t\t'\t\tconst repaired = await ensureLocalQaUser();',\n\t\t'\t\tif (repaired) {',\n\t\t'\t\t\tloginJson = await requestJson(`${serverUrl}/login`, { username, password });',\n\t\t'\t\t}',\n\t\t'\t}',\n\t\t'\tconst refreshToken = loginJson && loginJson.result && loginJson.result.token;',\n\t\t'\tif (loginJson.error || !refreshToken) {',\n\t\t'\t\tthrow new Error(`Login failed: ${JSON.stringify(loginJson).slice(0, 800)}`);',\n\t\t'\t}',\n\t\t'\tconst accessJson = await requestJson(`${serverUrl}/accessToken`, { refreshToken });',\n\t\t'\tconst accessToken = accessJson && accessJson.result && accessJson.result.token;',\n\t\t'\tconst user = accessJson && accessJson.result && accessJson.result.user;',\n\t\t'\tif (accessJson.error || !accessToken || !user) {',\n\t\t'\t\tthrow new Error(`Access token failed: ${JSON.stringify(accessJson).slice(0, 800)}`);',\n\t\t'\t}',\n\t\t'\treturn { refreshToken, accessToken, user: normalizeQaUserForBrowser(user) };',\n\t\t'}',\n\t\t'',\n\t\t'async function launchBrowser(puppeteer) {',\n\t\t'\tconst browserUrl = process.env.RESOLVEIO_RUNNER_QA_BROWSER_URL || process.env.RESOLVEIO_SUPPORT_QA_BROWSER_URL || \"\";',\n\t\t'\tif (browserUrl) {',\n\t\t'\t\treturn puppeteer.connect({ browserURL: browserUrl, protocolTimeout: 30000, defaultViewport: null });',\n\t\t'\t}',\n\t\t'\tconst launchOptions = {',\n\t\t'\t\theadless: true,',\n\t\t'\t\tdefaultViewport: { width: viewportWidth, height: viewportHeight },',\n\t\t'\t\targs: [\"--no-sandbox\", \"--disable-setuid-sandbox\", \"--disable-dev-shm-usage\", `--window-size=${viewportWidth},${viewportHeight}`]',\n\t\t'\t};',\n\t\t'\tif (process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN) {',\n\t\t'\t\tlaunchOptions.executablePath = process.env.PUPPETEER_EXECUTABLE_PATH || process.env.CHROME_BIN;',\n\t\t'\t}',\n\t\t'\treturn puppeteer.launch(launchOptions);',\n\t\t'}',\n\t\t'',\n\t\t'async function resetBrowserState(page) {',\n\t\t'\tawait waitForHttpReady(clientUrl, \"QA client\");',\n\t\t'\tawait page.goto(clientUrl, { waitUntil: \"domcontentloaded\", timeout: 45000 });',\n\t\t'\tawait page.evaluate(async () => {',\n\t\t'\t\ttry {',\n\t\t'\t\t\tconst registrations = await navigator.serviceWorker.getRegistrations();',\n\t\t'\t\t\tawait Promise.all(registrations.map((registration) => registration.unregister().catch(() => false)));',\n\t\t'\t\t} catch (error) {}',\n\t\t'\t\ttry {',\n\t\t'\t\t\tif (window.caches && window.caches.keys) {',\n\t\t'\t\t\t\tconst keys = await window.caches.keys();',\n\t\t'\t\t\t\tawait Promise.all(keys.map((key) => window.caches.delete(key).catch(() => false)));',\n\t\t'\t\t\t}',\n\t\t'\t\t} catch (error) {}',\n\t\t'\t\ttry {',\n\t\t'\t\t\tif (window.indexedDB && window.indexedDB.databases) {',\n\t\t'\t\t\t\tconst databases = await window.indexedDB.databases();',\n\t\t'\t\t\t\tawait Promise.all(databases.filter((database) => database && database.name).map((database) => new Promise((resolve) => {',\n\t\t'\t\t\t\t\tconst request = window.indexedDB.deleteDatabase(database.name);',\n\t\t'\t\t\t\t\trequest.onsuccess = () => resolve(true);',\n\t\t'\t\t\t\t\trequest.onerror = () => resolve(false);',\n\t\t'\t\t\t\t\trequest.onblocked = () => resolve(false);',\n\t\t'\t\t\t\t})));',\n\t\t'\t\t\t}',\n\t\t'\t\t} catch (error) {}',\n\t\t'\t\tlocalStorage.clear();',\n\t\t'\t\tsessionStorage.clear();',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'async function seedAuth(page, auth) {',\n\t\t'\tauth.user = normalizeQaUserForBrowser(auth.user);',\n\t\t'\twriteAuthStorageState(auth);',\n\t\t'\tawait page.evaluate((payload) => {',\n\t\t'\t\tlocalStorage.setItem(\"refreshToken\", payload.refreshToken);',\n\t\t'\t\tlocalStorage.setItem(\"accessToken\", payload.accessToken);',\n\t\t'\t\tlocalStorage.setItem(\"user\", JSON.stringify(payload.user));',\n\t\t'\t\tlocalStorage.setItem(\"lastURL\", payload.lastURL);',\n\t\t'\t\tlocalStorage.setItem(\"resolveio.runnerQaAuthBootstrappedAt\", payload.bootstrappedAt);',\n\t\t'\t}, { ...auth, lastURL: targetRoute, bootstrappedAt: new Date().toISOString() });',\n\t\t'}',\n\t\t'',\n\t\t'async function patchBrowserQaUser(page) {',\n\t\t'\tawait page.evaluate((nextRoute) => {',\n\t\t'\t\tlet user = {};',\n\t\t'\t\ttry { user = JSON.parse(localStorage.getItem(\"user\") || \"{}\"); } catch (error) { user = {}; }',\n\t\t'\t\tconst settings = {',\n\t\t'\t\t\ttable_color: \"#3b3ee3\",',\n\t\t'\t\t\ttable_font_color: \"#ffffff\",',\n\t\t'\t\t\tsecondary_table_color: \"#87ceeb\",',\n\t\t'\t\t\tsecondary_table_font_color: \"#000000\",',\n\t\t'\t\t\ttertiary_table_color: \"#ff4500\",',\n\t\t'\t\t\ttertiary_table_font_color: \"#000000\",',\n\t\t'\t\t\tfont_size: 12,',\n\t\t'\t\t\tcollapsable_menu: true,',\n\t\t'\t\t\tentries_per_page: \"25\",',\n\t\t'\t\t\twarning_color: \"#ffc107\",',\n\t\t'\t\t\twarning_font_color: \"#000000\",',\n\t\t'\t\t\twarning_hover_color: \"#e0a800\",',\n\t\t'\t\t\tsuccess_color: \"#28a745\",',\n\t\t'\t\t\tsuccess_font_color: \"#ffffff\",',\n\t\t'\t\t\tsuccess_hover_color: \"#218838\",',\n\t\t'\t\t\tdanger_color: \"#dc3545\",',\n\t\t'\t\t\tdanger_font_color: \"#ffffff\",',\n\t\t'\t\t\tdanger_hover_color: \"#c82333\",',\n\t\t'\t\t\tinfo_color: \"#17a2b8\",',\n\t\t'\t\t\tinfo_font_color: \"#ffffff\",',\n\t\t'\t\t\tinfo_hover_color: \"#138496\",',\n\t\t'\t\t\tprimary_color: \"#007bff\",',\n\t\t'\t\t\tprimary_font_color: \"#ffffff\",',\n\t\t'\t\t\tprimary_hover_color: \"#0069d9\",',\n\t\t'\t\t\tsecondary_color: \"#868e96\",',\n\t\t'\t\t\tsecondary_font_color: \"#ffffff\",',\n\t\t'\t\t\tsecondary_hover_color: \"#5a6268\",',\n\t\t'\t\t\trouting_preference: \"\",',\n\t\t'\t\t\topening_route: nextRoute,',\n\t\t'\t\t\trio_select_search_mode: \"exact\"',\n\t\t'\t\t};',\n\t\t'\t\tuser = {',\n\t\t'\t\t\t...user,',\n\t\t'\t\t\tother: {',\n\t\t'\t\t\t\t...(user.other || {}),',\n\t\t'\t\t\t\tyards: Array.isArray(user.other && user.other.yards) ? user.other.yards : [],',\n\t\t'\t\t\t\ttour_completed: true,',\n\t\t'\t\t\t\ttook_tour: true,',\n\t\t'\t\t\t\tcore_tour_completed: true,',\n\t\t'\t\t\t\twelcome_tour_completed: true,',\n\t\t'\t\t\t\ttop_navigation_tour_completed: true,',\n\t\t'\t\t\t\tuser_settings_tour_completed: true',\n\t\t'\t\t\t},',\n\t\t'\t\t\tsettings: { ...settings, ...(user.settings || {}), opening_route: nextRoute }',\n\t\t'\t\t};',\n\t\t'\t\tlocalStorage.setItem(\"user\", JSON.stringify(user));',\n\t\t'\t\tlocalStorage.setItem(\"lastURL\", nextRoute);',\n\t\t'\t\tlocalStorage.setItem(\"resolveio.runnerQaPostLoginGateRepairedAt\", new Date().toISOString());',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'async function dismissVisibleTourOrSetupGate(page) {',\n\t\t'\treturn page.evaluate(() => {',\n\t\t'\t\tconst controls = Array.from(document.querySelectorAll(\"button, a, [role=\\'button\\']\"));',\n\t\t'\t\tconst control = controls.find((candidate) => {',\n\t\t'\t\t\tconst text = (candidate.textContent || \"\").replace(/\\\\s+/g, \" \").trim();',\n\t\t'\t\t\treturn /^(skip|finish|done|close)$/i.test(text);',\n\t\t'\t\t});',\n\t\t'\t\tif (control && typeof control.click === \"function\") {',\n\t\t'\t\t\tcontrol.click();',\n\t\t'\t\t\treturn true;',\n\t\t'\t\t}',\n\t\t'\t\treturn false;',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'function isPostLoginSetupOrTourGate(currentRoute, summary) {',\n\t\t'\tconst text = String(summary && summary.bodyTextSnippet || \"\");',\n\t\t'\treturn currentRoute === \"/user-settings/settings\" || /Top Navigation Step \\\\d+ of \\\\d+|\\\\bSkip\\\\b|User Settings/i.test(text);',\n\t\t'}',\n\t\t'',\n\t\t'async function repairPostLoginSetupOrTourGate(page, expectedRoute) {',\n\t\t'\tif (process.env.RESOLVEIO_RUNNER_QA_DISABLE_POST_LOGIN_ROUTE_REPAIR === \"true\" || process.env.RESOLVEIO_SUPPORT_QA_DISABLE_POST_LOGIN_ROUTE_REPAIR === \"true\") {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'\tconst current = normalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\tconst summary = await pageSummary(page).catch(() => ({}));',\n\t\t'\tif (!isPostLoginSetupOrTourGate(current, summary)) {',\n\t\t'\t\treturn false;',\n\t\t'\t}',\n\t\t'\tawait ensureLocalQaUser().catch(() => false);',\n\t\t'\tawait refreshAuthAndSeed(page).catch(() => undefined);',\n\t\t'\tawait dismissVisibleTourOrSetupGate(page).catch(() => false);',\n\t\t'\tawait patchBrowserQaUser(page);',\n\t\t'\tawait page.goto(`${clientUrl}${expectedRoute}`, { waitUntil: \"domcontentloaded\", timeout: 60000 });',\n\t\t'\tawait delay(1500);',\n\t\t'\tawait dismissVisibleTourOrSetupGate(page).catch(() => false);',\n\t\t'\tawait patchBrowserQaUser(page);',\n\t\t'\tawait delay(500);',\n\t\t'\tconst repairedCurrent = canonicalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\treturn routesMatch(repairedCurrent, expectedRoute);',\n\t\t'}',\n\t\t'',\n\t\t'function delay(ms) {',\n\t\t'\treturn new Promise((resolve) => setTimeout(resolve, ms));',\n\t\t'}',\n\t\t'',\n\t\t'function normalizeRoutePath(value) {',\n\t\t'\ttry {',\n\t\t'\t\t\tconst parsed = new URL(value, clientUrl);',\n\t\t'\t\t\tlet pathname = parsed.pathname || \"/\";',\n\t\t'\t\t\tpathname = pathname.replace(/\\\\/+$/, \"\") || \"/\";',\n\t\t'\t\t\treturn pathname;',\n\t\t'\t}',\n\t\t'\tcatch (error) {',\n\t\t'\t\t\treturn String(value || \"/\").split(\"?\")[0].replace(/\\\\/+$/, \"\") || \"/\";',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'function canonicalizeRoutePath(value) {',\n\t\t'\tconst route = normalizeRoutePath(value);',\n\t\t'\tconst aliases = {',\n\t\t'\t\t\"/billing\": \"/dashboard/billing\",',\n\t\t'\t\t\"/invoice\": \"/dashboard/billing\",',\n\t\t'\t\t\"/invoices\": \"/dashboard/billing\",',\n\t\t'\t\t\"/customer\": \"/manage/customer\",',\n\t\t'\t\t\"/customers\": \"/manage/customer\",',\n\t\t'\t\t\"/pricing\": \"/manage/pricing\",',\n\t\t'\t\t\"/items\": \"/manage/item\",',\n\t\t'\t\t\"/invoice-items\": \"/manage/item\",',\n\t\t'\t\t\"/inventory\": \"/manage/inventory\"',\n\t\t'\t};',\n\t\t'\treturn aliases[route] || route;',\n\t\t'}',\n\t\t'',\n\t\t'function routesMatch(currentRoute, expectedRoute) {',\n\t\t'\treturn canonicalizeRoutePath(currentRoute) === canonicalizeRoutePath(expectedRoute);',\n\t\t'}',\n\t\t'',\n\t\t'function isGenericAuthTargetRoute(routePath) {',\n\t\t'\treturn [\"/\", \"/screen\", \"/home\"].includes(canonicalizeRoutePath(routePath));',\n\t\t'}',\n\t\t'',\n\t\t'function isAuthenticatedNonSetupRoute(summary) {',\n\t\t'\tconst current = normalizeRoutePath(summary && summary.url || \"\");',\n\t\t'\treturn !!summary',\n\t\t'\t\t&& !!summary.hasRefreshToken',\n\t\t'\t\t&& !!summary.hasAccessToken',\n\t\t'\t\t&& !!summary.hasUser',\n\t\t'\t\t&& !summary.hasLoginText',\n\t\t'\t\t&& !summary.hasOfflineModeText',\n\t\t'\t\t&& current !== \"/user-settings/settings\";',\n\t\t'}',\n\t\t'',\n\t\t'async function assertTargetRoute(page) {',\n\t\t'\tconst expected = canonicalizeRoutePath(targetRoute);',\n\t\t'\tif (expected === \"/\") {',\n\t\t'\t\treturn;',\n\t\t'\t}',\n\t\t'\tconst current = canonicalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\tif (!routesMatch(current, expected)) {',\n\t\t'\t\tconst repaired = await repairPostLoginSetupOrTourGate(page, expected);',\n\t\t'\t\tif (repaired) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tconst summary = await pageSummary(page).catch(() => ({}));',\n\t\t'\t\tif (isGenericAuthTargetRoute(expected) && isAuthenticatedNonSetupRoute(summary)) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tthrow new Error(`QA auth bootstrap reached ${current}, not requested target route ${expected}. This is a route blocker; do not continue browser QA until the runner/app can reach the requested screen. Page summary: ${JSON.stringify(summary).slice(0, 1200)}`);',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'async function waitForStableTargetRoute(page) {',\n\t\t'\tconst expected = canonicalizeRoutePath(targetRoute);',\n\t\t'\tif (isGenericAuthTargetRoute(expected)) {',\n\t\t'\t\treturn;',\n\t\t'\t}',\n\t\t'\tconst deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_STABILITY_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_STABILITY_TIMEOUT_MS || 7000);',\n\t\t'\tlet stableSince = 0;',\n\t\t'\tlet lastCurrent = \"\";',\n\t\t'\twhile (Date.now() < deadline) {',\n\t\t'\t\tconst current = canonicalizeRoutePath(await page.evaluate(() => location.href));',\n\t\t'\t\tlastCurrent = current;',\n\t\t'\t\tif (routesMatch(current, expected)) {',\n\t\t'\t\t\tstableSince = stableSince || Date.now();',\n\t\t'\t\t\tif (Date.now() - stableSince >= Number(process.env.RESOLVEIO_RUNNER_QA_ROUTE_STABLE_MS || process.env.RESOLVEIO_SUPPORT_QA_ROUTE_STABLE_MS || 2500)) {',\n\t\t'\t\t\t\treturn;',\n\t\t'\t\t\t}',\n\t\t'\t\t}',\n\t\t'\t\telse {',\n\t\t'\t\t\tstableSince = 0;',\n\t\t'\t\t}',\n\t\t'\t\tawait delay(250);',\n\t\t'\t}',\n\t\t'\tconst summary = await pageSummary(page).catch(() => ({}));',\n\t\t'\tthrow new Error(`QA auth bootstrap route was not stable on requested target ${expected}; last route was ${lastCurrent || \"unknown\"}. This is a route blocker; do not continue browser QA until the runner/app can remain on the requested screen. Page summary: ${JSON.stringify(summary).slice(0, 1200)}`);',\n\t\t'}',\n\t\t'',\n\t\t'async function dismissNavigationOverlays(page) {',\n\t\t'\ttry {',\n\t\t'\t\tawait page.keyboard.press(\"Escape\");',\n\t\t'\t} catch (error) {}',\n\t\t'\ttry {',\n\t\t'\t\tawait page.mouse.move(16, Math.max(120, viewportHeight - 24));',\n\t\t'\t} catch (error) {}',\n\t\t'\ttry {',\n\t\t'\t\tawait page.evaluate(() => {',\n\t\t'\t\t\tif (document.activeElement && typeof document.activeElement.blur === \"function\") document.activeElement.blur();',\n\t\t'\t\t\tdocument.body && document.body.dispatchEvent(new MouseEvent(\"mousemove\", { bubbles: true, clientX: 16, clientY: Math.max(120, window.innerHeight - 24) }));',\n\t\t'\t\t});',\n\t\t'\t} catch (error) {}',\n\t\t'\tawait delay(350);',\n\t\t'}',\n\t\t'',\n\t\t'async function assertSummaryOnTargetRoute(summary) {',\n\t\t'\tconst expected = canonicalizeRoutePath(targetRoute);',\n\t\t'\tif (expected === \"/\") {',\n\t\t'\t\treturn;',\n\t\t'\t}',\n\t\t'\tconst current = canonicalizeRoutePath(summary && summary.url || \"\");',\n\t\t'\tif (!routesMatch(current, expected)) {',\n\t\t'\t\tif (isGenericAuthTargetRoute(expected) && isAuthenticatedNonSetupRoute(summary)) {',\n\t\t'\t\t\treturn;',\n\t\t'\t\t}',\n\t\t'\t\tthrow new Error(`QA auth bootstrap final summary is on ${current}, not requested target route ${expected}. This is a route blocker; refusing to write a false pass. Page summary: ${JSON.stringify(summary).slice(0, 1200)}`);',\n\t\t'\t}',\n\t\t'}',\n\t\t'',\n\t\t'async function logoutExistingBrowserSession(page) {',\n\t\t'\ttry {',\n\t\t'\t\tawait page.goto(clientUrl, { waitUntil: \"domcontentloaded\", timeout: 45000 });',\n\t\t'\t\tawait delay(500);',\n\t\t'\t\tawait page.evaluate(() => {',\n\t\t'\t\t\tconst controls = Array.from(document.querySelectorAll(\"button, a, [role=\\'button\\']\"));',\n\t\t'\t\t\tconst logoutControl = controls.find((control) => /(^|\\\\s)logout(\\\\s|$)/i.test((control.textContent || \"\").trim()));',\n\t\t'\t\t\tif (logoutControl && typeof logoutControl.click === \"function\") {',\n\t\t'\t\t\t\tlogoutControl.click();',\n\t\t'\t\t\t\treturn true;',\n\t\t'\t\t\t}',\n\t\t'\t\t\treturn false;',\n\t\t'\t\t});',\n\t\t'\t\tawait delay(1000);',\n\t\t'\t}',\n\t\t'\tcatch (error) {}',\n\t\t'}',\n\t\t'',\n\t\t'async function waitForAuthenticatedApp(page) {',\n\t\t'\tconst url = `${clientUrl}${targetRoute}`;',\n\t\t'\tawait page.goto(url, { waitUntil: \"domcontentloaded\", timeout: 60000 });',\n\t\t'\tawait page.waitForFunction(() => {',\n\t\t'\t\tconst text = (document.body && document.body.innerText || \"\").replace(/\\\\s+/g, \" \").trim();',\n\t\t'\t\tconst hasTokens = !!localStorage.getItem(\"refreshToken\") && !!localStorage.getItem(\"accessToken\") && !!localStorage.getItem(\"user\");',\n\t\t'\t\tconst hasLogin = /Employee\\\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(text);',\n\t\t'\t\tconst hasOffline = text.includes(\"*** OFFLINE MODE ***\");',\n\t\t'\t\treturn hasTokens && !hasLogin && !hasOffline && text.length > 40;',\n\t\t'\t}, { timeout: Number(process.env.RESOLVEIO_RUNNER_QA_AUTH_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_AUTH_TIMEOUT_MS || 60000) });',\n\t\t'\tawait delay(1000);',\n\t\t'\tawait assertTargetRoute(page);',\n\t\t'\tawait waitForStableTargetRoute(page);',\n\t\t'\tawait dismissNavigationOverlays(page);',\n\t\t'}',\n\t\t'',\n\t\t'async function pageSummary(page) {',\n\t\t'\treturn page.evaluate(() => {',\n\t\t'\t\tconst bodyText = (document.body && document.body.innerText || \"\").replace(/\\\\s+/g, \" \").trim();',\n\t\t'\t\treturn {',\n\t\t'\t\t\turl: location.href,',\n\t\t'\t\t\ttitle: document.title,',\n\t\t'\t\t\thasAngularDebug: !!window.ng,',\n\t\t'\t\t\thasRefreshToken: !!localStorage.getItem(\"refreshToken\"),',\n\t\t'\t\t\thasAccessToken: !!localStorage.getItem(\"accessToken\"),',\n\t\t'\t\t\thasUser: !!localStorage.getItem(\"user\"),',\n\t\t'\t\t\thasOfflineModeText: bodyText.includes(\"*** OFFLINE MODE ***\"),',\n\t\t'\t\t\thasLoginText: /Employee\\\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(bodyText),',\n\t\t'\t\t\tbodyTextSnippet: bodyText.slice(0, 800),',\n\t\t'\t\t\tlocalStorageKeys: Object.keys(localStorage).sort()',\n\t\t'\t\t};',\n\t\t'\t});',\n\t\t'}',\n\t\t'',\n\t\t'(async () => {',\n\t\t'\tfs.mkdirSync(artifactDir, { recursive: true });',\n\t\t'\tawait waitForHttpReady(clientUrl, \"QA client\");',\n\t\t'\tawait waitForHttpReady(serverUrl, \"QA server\");',\n\t\t'\tconst puppeteer = requirePuppeteer();',\n\t\t'\tconst browser = await launchBrowser(puppeteer);',\n\t\t'\tlet page;',\n\t\t'\ttry {',\n\t\t'\t\tpage = await browser.newPage();',\n\t\t'\t\tawait page.setViewport({ width: viewportWidth, height: viewportHeight });',\n\t\t'\t\tpage.on(\"console\", (msg) => {',\n\t\t'\t\t\tconst text = msg.text();',\n\t\t'\t\t\tif ([\"error\", \"warning\"].includes(msg.type()) || /error/i.test(text)) {',\n\t\t'\t\t\t\tconsole.log(\"[browser console]\", msg.type(), text);',\n\t\t'\t\t\t}',\n\t\t'\t\t});',\n\t\t'\t\tpage.on(\"pageerror\", (error) => console.log(\"[pageerror]\", error.message));',\n\t\t'\t\tawait logoutExistingBrowserSession(page);',\n\t\t'\t\tawait resetBrowserState(page);',\n\t\t'\t\tconst auth = await login();',\n\t\t'\t\tawait seedAuth(page, auth);',\n\t\t'\t\tawait waitForAuthenticatedApp(page);',\n\t\t'\t\tconst finalPage = await pageSummary(page);',\n\t\t'\t\tawait assertSummaryOnTargetRoute(finalPage);',\n\t\t'\t\tawait page.screenshot({ path: readyScreenshotPath, type: \"jpeg\", quality: 82, fullPage: false });',\n\t\t'\t\tconst summary = {',\n\t\t'\t\t\tstatus: \"pass\",',\n\t\t'\t\t\tclientUrl,',\n\t\t'\t\t\tserverUrl,',\n\t\t'\t\t\ttargetRoute,',\n\t\t'\t\t\tscreenshot: readyScreenshotPath,',\n\t\t'\t\t\tstorageState: storageStatePath,',\n\t\t'\t\t\tuser: { _id: auth.user && auth.user._id, username: auth.user && auth.user.username, fullname: auth.user && auth.user.fullname },',\n\t\t'\t\t\tpage: finalPage',\n\t\t'\t\t};',\n\t\t'\t\twriteResult(summary);',\n\t\t'\t\tconsole.log(JSON.stringify(summary, null, 2));',\n\t\t'\t}',\n\t\t'\tcatch (error) {',\n\t\t'\t\tlet summary = { status: \"fail\", clientUrl, serverUrl, targetRoute, screenshot: failureScreenshotPath, error: error && error.stack || String(error) };',\n\t\t'\t\ttry {',\n\t\t'\t\t\tif (page) {',\n\t\t'\t\t\t\tawait page.screenshot({ path: failureScreenshotPath, type: \"jpeg\", quality: 82, fullPage: false });',\n\t\t'\t\t\t\tsummary.page = await pageSummary(page);',\n\t\t'\t\t\t}',\n\t\t'\t\t} catch (screenshotError) {',\n\t\t'\t\t\tsummary.screenshotError = screenshotError && screenshotError.stack || String(screenshotError);',\n\t\t'\t\t}',\n\t\t'\t\twriteResult(summary);',\n\t\t'\t\tconsole.error(JSON.stringify(summary, null, 2));',\n\t\t'\t\tprocess.exitCode = 1;',\n\t\t'\t}',\n\t\t'\tfinally {',\n\t\t'\t\tif (browser && typeof browser.close === \"function\") {',\n\t\t'\t\t\tawait browser.close().catch(() => undefined);',\n\t\t'\t\t}',\n\t\t'\t\tprocess.exit(process.exitCode || 0);',\n\t\t'\t}',\n\t\t'})();',\n\t\t''\n\t].join('\\n');\n}\n"]}
|
|
@@ -282,21 +282,13 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
282
282
|
' kill_env_marked_processes',
|
|
283
283
|
' kill_artifact_log_writers',
|
|
284
284
|
' kill_local_mongo_processes',
|
|
285
|
-
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
285
|
+
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng[[:space:]]+serve|esbuild --service|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
286
286
|
' skip_cleanup_pid "$pid" && continue',
|
|
287
287
|
' kill_tree "$pid"',
|
|
288
288
|
' done',
|
|
289
|
-
'
|
|
290
|
-
'
|
|
291
|
-
'
|
|
292
|
-
' pid="${pid%/cwd}"',
|
|
293
|
-
' skip_cleanup_pid "$pid" && continue',
|
|
294
|
-
' cwd="$(readlink -f "$proc_cwd" 2>/dev/null || true)"',
|
|
295
|
-
' case "$cwd" in',
|
|
296
|
-
' "$PROJECT_ROOT"|"$PROJECT_ROOT"/*) kill_tree "$pid" ;;',
|
|
297
|
-
' esac',
|
|
298
|
-
' done',
|
|
299
|
-
' fi',
|
|
289
|
+
' # Do not kill arbitrary processes by project cwd; completion builds run',
|
|
290
|
+
' # from the same cwd. Cleanup is limited to known QA process signatures,',
|
|
291
|
+
' # recorded PIDs, scoped ports, and QA runner env markers.',
|
|
300
292
|
' sleep 1',
|
|
301
293
|
' done',
|
|
302
294
|
' local wait_until=$((SECONDS + 60))',
|
|
@@ -326,7 +318,7 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
326
318
|
' fi',
|
|
327
319
|
' kill_artifact_log_writers',
|
|
328
320
|
' kill_local_mongo_processes',
|
|
329
|
-
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
321
|
+
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng[[:space:]]+serve|esbuild --service|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
330
322
|
' skip_cleanup_pid "$pid" && continue',
|
|
331
323
|
' found=1',
|
|
332
324
|
' done',
|
|
@@ -386,7 +378,7 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
386
378
|
'log_has_fatal() {',
|
|
387
379
|
' local file="$1"',
|
|
388
380
|
' [ -f "$file" ] || return 1',
|
|
389
|
-
' grep -Eiq "Unhandled Rejection|Cannot read properties of undefined|TypeError|ReferenceError|EADDRINUSE|app crashed|Failed to compile|
|
|
381
|
+
' grep -Eiq "Unhandled Rejection|Cannot read properties of undefined|TypeError|ReferenceError|EADDRINUSE|app crashed|Failed to compile|Compilation failed|TypeScript:.*(semantic errors|Compilation failed)|error TS[0-9]{4}|NG[0-9]{4}|TS[0-9]{4}|Error: Cannot find module" "$file"',
|
|
390
382
|
'}',
|
|
391
383
|
'prepare_angular_cache_dirs() {',
|
|
392
384
|
' [ -d "$PROJECT_ROOT" ] || return 0',
|
|
@@ -632,22 +624,14 @@ function buildResolveIORunnerLocalQaStopperScript() {
|
|
|
632
624
|
' kill_env_marked_processes',
|
|
633
625
|
' kill_artifact_log_writers',
|
|
634
626
|
' kill_local_mongo_processes',
|
|
635
|
-
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
627
|
+
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng[[:space:]]+serve|esbuild --service|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
636
628
|
' skip_cleanup_pid "$pid" && continue',
|
|
637
629
|
' kill_tree "$pid"',
|
|
638
630
|
' killed_count=$((killed_count + 1))',
|
|
639
631
|
' done',
|
|
640
|
-
'
|
|
641
|
-
'
|
|
642
|
-
'
|
|
643
|
-
' pid="${pid%/cwd}"',
|
|
644
|
-
' skip_cleanup_pid "$pid" && continue',
|
|
645
|
-
' cwd="$(readlink -f "$proc_cwd" 2>/dev/null || true)"',
|
|
646
|
-
' case "$cwd" in',
|
|
647
|
-
' "$PROJECT_ROOT"|"$PROJECT_ROOT"/*) kill_tree "$pid" ;;',
|
|
648
|
-
' esac',
|
|
649
|
-
' done',
|
|
650
|
-
' fi',
|
|
632
|
+
' # Do not kill arbitrary processes by project cwd; completion builds run',
|
|
633
|
+
' # from the same cwd. Stop only known QA process signatures, recorded PIDs,',
|
|
634
|
+
' # scoped ports, and QA runner env markers.',
|
|
651
635
|
' sleep 1',
|
|
652
636
|
'done',
|
|
653
637
|
'wait_until=$((SECONDS + 60))',
|
|
@@ -677,7 +661,7 @@ function buildResolveIORunnerLocalQaStopperScript() {
|
|
|
677
661
|
' fi',
|
|
678
662
|
' kill_artifact_log_writers',
|
|
679
663
|
' kill_local_mongo_processes',
|
|
680
|
-
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng|esbuild|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
664
|
+
' for pid in $(ps -eo pid=,args= | awk -v root="$PROJECT_ROOT" \'index($0, root) && $0 !~ /awk -v root=/ && $0 !~ /ps -eo pid=,args=/ && $0 !~ /run-local-qa\\.sh|stop-local-qa\\.sh|bugfix-comparison-qa\\.sh/ && $0 ~ /(ng serve|node .*node_modules\\/\\.bin\\/ng[[:space:]]+serve|esbuild --service|npm run client|start_client\\.sh|npm run server|start_server\\.sh|nodemon|node .*tmp\\/index\\.js|mongod|mongodb-binaries\\/mongod)/ {print $1}\' 2>/dev/null || true); do',
|
|
681
665
|
' skip_cleanup_pid "$pid" && continue',
|
|
682
666
|
' found=1',
|
|
683
667
|
' done',
|
|
@@ -728,6 +712,9 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
728
712
|
'function preserveExistingSeedResult(reason) {',
|
|
729
713
|
' const existing = readJsonIfExists(resultPath);',
|
|
730
714
|
' const status = String(existing && existing.status || "").toLowerCase();',
|
|
715
|
+
' if (existing && existing.profile === "billing_inventory" && !(existing.selected && existing.selected.qa_billing_fixture)) {',
|
|
716
|
+
' return;',
|
|
717
|
+
' }',
|
|
731
718
|
' if (["pass", "needs-data"].includes(status)) {',
|
|
732
719
|
' const preserved = { ...existing, reused_existing: true, reuse_reason: reason, checked_at: new Date().toISOString() };',
|
|
733
720
|
' console.log(JSON.stringify(preserved, null, 2));',
|
|
@@ -898,6 +885,51 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
898
885
|
' if (pricingDocs.length) summary.notes.push(`Ensured ${pricingDocs.length} local pricing-items for service/misc surcharge override/default QA.`);',
|
|
899
886
|
'}',
|
|
900
887
|
'',
|
|
888
|
+
'async function ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, serviceItems) {',
|
|
889
|
+
' const now = new Date();',
|
|
890
|
+
' const customerId = (customerIds && customerIds[0]) || "qa-billing-customer";',
|
|
891
|
+
' const yardId = (yardIds && yardIds[0]) || "qa-billing-yard";',
|
|
892
|
+
' const item = (serviceItems || []).find((doc) => doc && doc._id) || { _id: "qa-billing-service-item", name: "QA Billing Service Item", unit: "Each", type: "Service", active: true };',
|
|
893
|
+
' const locationId = "qa-billing-location";',
|
|
894
|
+
' const bolId = "qa-billing-bol";',
|
|
895
|
+
' const psoId = "qa-billing-pso-awaiting-invoice";',
|
|
896
|
+
' const deliveryId = "qa-billing-delivery-awaiting-invoice";',
|
|
897
|
+
' const invoiceId = "qa-billing-existing-processing-invoice";',
|
|
898
|
+
' await targetDb.collection("customers").replaceOne({ _id: customerId }, {',
|
|
899
|
+
' _id: customerId, name: "QA Billing Customer", active: true, send_type: "Email", customer_approval_required: false, createdAt: now, updatedAt: now',
|
|
900
|
+
' }, { upsert: true });',
|
|
901
|
+
' await targetDb.collection("yards").replaceOne({ _id: yardId }, { _id: yardId, name: "QA Billing Yard", active: true, createdAt: now, updatedAt: now }, { upsert: true });',
|
|
902
|
+
' await targetDb.collection("items").replaceOne({ _id: item._id }, { ...item, _id: item._id, name: item.name || "QA Billing Service Item", unit: item.unit || "Each", type: item.type || "Service", active: true, updatedAt: now }, { upsert: true });',
|
|
903
|
+
' await targetDb.collection("production-locations").replaceOne({ _id: locationId }, {',
|
|
904
|
+
' _id: locationId, id_customer: customerId, customer: "QA Billing Customer", location: "QA Billing Location", id_yard: yardId, county: "QA County", active: true, createdAt: now, updatedAt: now',
|
|
905
|
+
' }, { upsert: true });',
|
|
906
|
+
' await targetDb.collection("bols").replaceOne({ _id: bolId }, {',
|
|
907
|
+
' _id: bolId, bol_string: "QA-BOL-004333", bol_number: "QA-BOL-004333", status: "Delivered", date: now, driver: "QA Driver", createdAt: now, updatedAt: now',
|
|
908
|
+
' }, { upsert: true });',
|
|
909
|
+
' const lineItem = {',
|
|
910
|
+
' _id: "qa-billing-pso-line-1", id_item: item._id, item: item.name || "QA Billing Service Item", item_type: item.type || "Service", type: item.type || "Service", quantity: 2, unit: item.unit || "Each", price: 100, price_subtotal: 200, price_total: 216.5, tax_amount: 16.5, billed: true, memo_bill: false, taxable: true, id_location: locationId, location: "QA Billing Location", id_yard: yardId, yard: "QA Billing Yard"',
|
|
911
|
+
' };',
|
|
912
|
+
' await targetDb.collection("production-sales-orders").replaceOne({ _id: psoId }, {',
|
|
913
|
+
' _id: psoId, status: "Awaiting Invoice", type: "Invoice", id_customer: customerId, customer: "QA Billing Customer", id_default_yard: yardId, date_created: now, date_ship: now, date_needed: now, order_number_string: "QA-PSO-004333", activity_number: "QA-PSO-004333", bol_numbers: "QA-BOL-004333", drivers: "QA Driver", ship_to_type: "Location", ship_to_location: "QA Billing Location", price: 200, price_subtotal: 200, account_manager: "QA Admin", user_approved: "QA Admin", production_locations: [{ id_location: locationId, location: "QA Billing Location", items: [lineItem] }], createdAt: now, updatedAt: now',
|
|
914
|
+
' }, { upsert: true });',
|
|
915
|
+
' await targetDb.collection("production-deliveries").replaceOne({ _id: deliveryId }, {',
|
|
916
|
+
' _id: deliveryId, type: "Delivery", approved: true, billed: true, memo_bill: false, invoiced: false, generic: false, invoices: [], consignment: false, id_activity: psoId, activity_number: "QA-PSO-004333", activity_type: "Invoice", id_customer: customerId, id_location: locationId, location: "QA Billing Location", id_yard: yardId, yard: "QA Billing Yard", id_item: item._id, item: item.name || "QA Billing Service Item", item_type: item.type || "Service", quantity: 2, unit: item.unit || "Each", price_per_unit: 100, price_subtotal: 200, price_total: 216.5, tax_amount: 16.5, id_bol: bolId, bol_number: "QA-BOL-004333", date: now, date_ship: now, user_approved: "QA Admin", account_manager: "QA Admin", createdAt: now, updatedAt: now',
|
|
917
|
+
' }, { upsert: true });',
|
|
918
|
+
' await targetDb.collection("invoices").replaceOne({ _id: invoiceId }, {',
|
|
919
|
+
' _id: invoiceId, status: "Processing", type: "Invoice", id_customer: customerId, customer: "QA Billing Customer", id_yard: yardId, yard: "QA Billing Yard", invoice_string: "QA-INV-004333", invoice_number: 4333, date_invoice: now, total: 216.5, subtotal: 200, tax_amount: 16.5, printed: false, invoice_opened: false, logs: [], files: [], line_items: [{ ...lineItem, source_production_delivery_id: deliveryId, description: lineItem.item, date: now }], createdAt: now, updatedAt: now',
|
|
920
|
+
' }, { upsert: true });',
|
|
921
|
+
' summary.collections.customers = (summary.collections.customers || 0) + 1;',
|
|
922
|
+
' summary.collections.yards = (summary.collections.yards || 0) + 1;',
|
|
923
|
+
' summary.collections.items = (summary.collections.items || 0) + 1;',
|
|
924
|
+
' summary.collections["production-locations"] = (summary.collections["production-locations"] || 0) + 1;',
|
|
925
|
+
' summary.collections.bols = (summary.collections.bols || 0) + 1;',
|
|
926
|
+
' summary.collections["production-sales-orders"] = (summary.collections["production-sales-orders"] || 0) + 1;',
|
|
927
|
+
' summary.collections["production-deliveries"] = (summary.collections["production-deliveries"] || 0) + 1;',
|
|
928
|
+
' summary.collections.invoices = (summary.collections.invoices || 0) + 1;',
|
|
929
|
+
' summary.selected.qa_billing_fixture = { customerId, yardId, psoId, deliveryId, invoiceId, itemId: item._id };',
|
|
930
|
+
' summary.notes.push("Ensured localhost-only Billing Dashboard fixtures: one existing processing invoice plus one awaiting-invoice PSO/delivery source row.");',
|
|
931
|
+
'}',
|
|
932
|
+
'',
|
|
901
933
|
'async function selectDashboardReadyProductionDeliveryIds(sourceDb, query, limit = 5) {',
|
|
902
934
|
' const rows = await sourceDb.collection("production-deliveries").aggregate([',
|
|
903
935
|
' { $match: query || {} },',
|
|
@@ -1035,6 +1067,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
1035
1067
|
' { name: /surcharge|fuel|delivery|service|misc/i }',
|
|
1036
1068
|
' ] }, summary, 80, { updatedAt: -1 });',
|
|
1037
1069
|
' await ensureBillingSurchargePricingFixtures(targetDb, summary, copiedServiceItems, customerIds);',
|
|
1070
|
+
' await ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, copiedServiceItems);',
|
|
1038
1071
|
'',
|
|
1039
1072
|
' const inventoryLocationQuery = { $or: [',
|
|
1040
1073
|
' { id_item: { $in: unique([...itemIds, ...copiedItems.map((doc) => doc._id), ...copiedServiceItems.map((doc) => doc._id)]) } },',
|
|
@@ -1337,6 +1370,35 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
1337
1370
|
' return { url: location.href, title: document.title, bodyTextSnippet: bodyText.slice(0, 1200), hasLoginText: /Employee\\/Customer Login|Employee Sign In|Customer Access|Unable to sign in/i.test(bodyText), hasOfflineModeText: bodyText.includes("*** OFFLINE MODE ***") };',
|
|
1338
1371
|
' });',
|
|
1339
1372
|
'}',
|
|
1373
|
+
'function billingDashboardHasVisibleWork(text) {',
|
|
1374
|
+
' const value = String(text || "");',
|
|
1375
|
+
' const patterns = [',
|
|
1376
|
+
' /PSO Type Invoice Awaiting Invoices, Customers: \\((\\d+)\\), Deliveries: \\((\\d+)\\)/i,',
|
|
1377
|
+
' /Deliveries Awaiting Invoices, Customers: \\((\\d+)\\), Treatments: \\((\\d+)\\)/i,',
|
|
1378
|
+
' /Truck Treatments Awaiting Invoices, Customers: \\((\\d+)\\), Treatments: \\((\\d+)\\)/i,',
|
|
1379
|
+
' /Periodic Billing, Customers: \\((\\d+)\\), Items: \\((\\d+)\\)/i,',
|
|
1380
|
+
' /Custom Consolidated Invoicing, Customers: \\((\\d+)\\), Items: \\((\\d+)\\)/i,',
|
|
1381
|
+
' /Processing, Customers: \\((\\d+)\\), Invoices: \\((\\d+)\\)/i,',
|
|
1382
|
+
' /Prepared, Customers: \\((\\d+)\\), Invoices: \\((\\d+)\\)/i,',
|
|
1383
|
+
' /Open Invoices, Customers: \\((\\d+)\\), Invoices: \\((\\d+)\\)/i',
|
|
1384
|
+
' ];',
|
|
1385
|
+
' for (const pattern of patterns) {',
|
|
1386
|
+
' const match = pattern.exec(value);',
|
|
1387
|
+
' if (match && (Number(match[1]) > 0 || Number(match[2]) > 0)) return true;',
|
|
1388
|
+
' }',
|
|
1389
|
+
' return false;',
|
|
1390
|
+
'}',
|
|
1391
|
+
'async function waitForBillingDashboardWork(page) {',
|
|
1392
|
+
' if (!/\\/billing(?:$|[?#])|\\/dashboard\\/billing(?:$|[?#])/.test(targetRoute)) return null;',
|
|
1393
|
+
' const deadline = Date.now() + Number(process.env.RESOLVEIO_RUNNER_QA_BILLING_DATA_TIMEOUT_MS || process.env.RESOLVEIO_SUPPORT_QA_BILLING_DATA_TIMEOUT_MS || 45000);',
|
|
1394
|
+
' let summary = await pageSummary(page);',
|
|
1395
|
+
' while (Date.now() < deadline) {',
|
|
1396
|
+
' summary = await pageSummary(page);',
|
|
1397
|
+
' if (billingDashboardHasVisibleWork(summary.bodyTextSnippet)) return summary;',
|
|
1398
|
+
' await delay(1500);',
|
|
1399
|
+
' }',
|
|
1400
|
+
' throw new Error(`Billing Dashboard loaded but no actionable seeded billing rows became visible before QA handoff. This is a runner data-seeding blocker, not a feature failure. Page summary: ${JSON.stringify(summary).slice(0, 1000)}`);',
|
|
1401
|
+
'}',
|
|
1340
1402
|
'function updateMatrix(status, screenshotPath, caption, assertion) {',
|
|
1341
1403
|
' const matrix = readJson(matrixPath) || { status: "started", rows: [] };',
|
|
1342
1404
|
' matrix.workflow_probe = { status, route: targetRoute, screenshot: screenshotPath, caption, assertion, updated_at: new Date().toISOString() };',
|
|
@@ -1366,8 +1428,9 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
1366
1428
|
' await page.goto(`${clientUrl}${targetRoute}`, { waitUntil: "domcontentloaded", timeout: 60000 });',
|
|
1367
1429
|
' await page.waitForSelector("body", { timeout: 30000 });',
|
|
1368
1430
|
' await delay(2500);',
|
|
1369
|
-
'
|
|
1431
|
+
' let summary = await pageSummary(page);',
|
|
1370
1432
|
' if (summary.hasLoginText || summary.hasOfflineModeText || !summary.bodyTextSnippet) throw new Error(`Workflow route did not reach authenticated app: ${JSON.stringify(summary).slice(0, 1000)}`);',
|
|
1433
|
+
' summary = await waitForBillingDashboardWork(page) || summary;',
|
|
1371
1434
|
' const caption = `Workflow route ready: ${targetRoute} loaded in authenticated local QA with live seeded data available.`;',
|
|
1372
1435
|
' await page.screenshot({ path: passScreenshotPath, type: "jpeg", quality: 82, fullPage: false });',
|
|
1373
1436
|
' updateMatrix("pass", passScreenshotPath, caption, "Authenticated customer workflow route loaded; deeper row-specific UI/data proof still required.");',
|