@pan-sec/notebooklm-mcp 2026.2.11 → 2026.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -17
- package/SECURITY.md +31 -61
- package/dist/auth/auth-manager.d.ts +2 -1
- package/dist/auth/auth-manager.d.ts.map +1 -1
- package/dist/auth/auth-manager.js +97 -42
- package/dist/auth/auth-manager.js.map +1 -1
- package/dist/auth/mcp-auth.d.ts +22 -4
- package/dist/auth/mcp-auth.d.ts.map +1 -1
- package/dist/auth/mcp-auth.js +120 -19
- package/dist/auth/mcp-auth.js.map +1 -1
- package/dist/compliance/alert-manager.d.ts.map +1 -1
- package/dist/compliance/alert-manager.js +7 -4
- package/dist/compliance/alert-manager.js.map +1 -1
- package/dist/compliance/breach-detection.d.ts.map +1 -1
- package/dist/compliance/breach-detection.js +14 -7
- package/dist/compliance/breach-detection.js.map +1 -1
- package/dist/compliance/change-log.d.ts.map +1 -1
- package/dist/compliance/change-log.js +7 -4
- package/dist/compliance/change-log.js.map +1 -1
- package/dist/compliance/compliance-logger.d.ts.map +1 -1
- package/dist/compliance/compliance-logger.js +11 -6
- package/dist/compliance/compliance-logger.js.map +1 -1
- package/dist/compliance/consent-manager.d.ts.map +1 -1
- package/dist/compliance/consent-manager.js +5 -3
- package/dist/compliance/consent-manager.js.map +1 -1
- package/dist/compliance/data-erasure.d.ts +1 -1
- package/dist/compliance/data-erasure.d.ts.map +1 -1
- package/dist/compliance/data-erasure.js +142 -83
- package/dist/compliance/data-erasure.js.map +1 -1
- package/dist/compliance/data-export.d.ts.map +1 -1
- package/dist/compliance/data-export.js +23 -12
- package/dist/compliance/data-export.js.map +1 -1
- package/dist/compliance/data-inventory.d.ts.map +1 -1
- package/dist/compliance/data-inventory.js +7 -6
- package/dist/compliance/data-inventory.js.map +1 -1
- package/dist/compliance/dsar-handler.d.ts +7 -1
- package/dist/compliance/dsar-handler.d.ts.map +1 -1
- package/dist/compliance/dsar-handler.js +74 -61
- package/dist/compliance/dsar-handler.js.map +1 -1
- package/dist/compliance/evidence-collector.d.ts.map +1 -1
- package/dist/compliance/evidence-collector.js +10 -6
- package/dist/compliance/evidence-collector.js.map +1 -1
- package/dist/compliance/health-monitor.d.ts.map +1 -1
- package/dist/compliance/health-monitor.js +15 -9
- package/dist/compliance/health-monitor.js.map +1 -1
- package/dist/compliance/incident-manager.d.ts.map +1 -1
- package/dist/compliance/incident-manager.js +5 -3
- package/dist/compliance/incident-manager.js.map +1 -1
- package/dist/compliance/policy-docs.d.ts.map +1 -1
- package/dist/compliance/policy-docs.js +14 -11
- package/dist/compliance/policy-docs.js.map +1 -1
- package/dist/compliance/privacy-notice-text.d.ts.map +1 -1
- package/dist/compliance/privacy-notice-text.js +3 -4
- package/dist/compliance/privacy-notice-text.js.map +1 -1
- package/dist/compliance/privacy-notice.d.ts.map +1 -1
- package/dist/compliance/privacy-notice.js +5 -3
- package/dist/compliance/privacy-notice.js.map +1 -1
- package/dist/compliance/report-generator.d.ts.map +1 -1
- package/dist/compliance/report-generator.js +5 -3
- package/dist/compliance/report-generator.js.map +1 -1
- package/dist/compliance/retention-engine.d.ts.map +1 -1
- package/dist/compliance/retention-engine.js +18 -10
- package/dist/compliance/retention-engine.js.map +1 -1
- package/dist/compliance/siem-exporter.d.ts.map +1 -1
- package/dist/compliance/siem-exporter.js +40 -16
- package/dist/compliance/siem-exporter.js.map +1 -1
- package/dist/config.d.ts +4 -31
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -63
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +21 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +54 -1
- package/dist/errors.js.map +1 -1
- package/dist/gemini/gemini-client.d.ts +1 -0
- package/dist/gemini/gemini-client.d.ts.map +1 -1
- package/dist/gemini/gemini-client.js +50 -49
- package/dist/gemini/gemini-client.js.map +1 -1
- package/dist/gemini/types.d.ts +3 -1
- package/dist/gemini/types.d.ts.map +1 -1
- package/dist/gemini/types.js.map +1 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +399 -85
- package/dist/index.js.map +1 -1
- package/dist/library/notebook-library.d.ts.map +1 -1
- package/dist/library/notebook-library.js +2 -1
- package/dist/library/notebook-library.js.map +1 -1
- package/dist/logging/query-logger.d.ts +13 -1
- package/dist/logging/query-logger.d.ts.map +1 -1
- package/dist/logging/query-logger.js +62 -10
- package/dist/logging/query-logger.js.map +1 -1
- package/dist/notebook-creation/audio-manager.d.ts.map +1 -1
- package/dist/notebook-creation/audio-manager.js +19 -24
- package/dist/notebook-creation/audio-manager.js.map +1 -1
- package/dist/notebook-creation/browser-options.d.ts +28 -0
- package/dist/notebook-creation/browser-options.d.ts.map +1 -0
- package/dist/notebook-creation/browser-options.js +75 -0
- package/dist/notebook-creation/browser-options.js.map +1 -0
- package/dist/notebook-creation/data-table-manager.d.ts.map +1 -1
- package/dist/notebook-creation/data-table-manager.js +20 -21
- package/dist/notebook-creation/data-table-manager.js.map +1 -1
- package/dist/notebook-creation/discover-creation-flow.d.ts +0 -6
- package/dist/notebook-creation/discover-creation-flow.d.ts.map +1 -1
- package/dist/notebook-creation/discover-creation-flow.js +10 -10
- package/dist/notebook-creation/discover-creation-flow.js.map +1 -1
- package/dist/notebook-creation/discover-quota.d.ts +0 -6
- package/dist/notebook-creation/discover-quota.d.ts.map +1 -1
- package/dist/notebook-creation/discover-quota.js +12 -13
- package/dist/notebook-creation/discover-quota.js.map +1 -1
- package/dist/notebook-creation/discover-sources.js +15 -16
- package/dist/notebook-creation/discover-sources.js.map +1 -1
- package/dist/notebook-creation/dom-scripts.d.ts +10 -0
- package/dist/notebook-creation/dom-scripts.d.ts.map +1 -0
- package/dist/notebook-creation/dom-scripts.js +58 -0
- package/dist/notebook-creation/dom-scripts.js.map +1 -0
- package/dist/notebook-creation/errors.d.ts +18 -0
- package/dist/notebook-creation/errors.d.ts.map +1 -0
- package/dist/notebook-creation/errors.js +20 -0
- package/dist/notebook-creation/errors.js.map +1 -0
- package/dist/notebook-creation/index.d.ts +2 -0
- package/dist/notebook-creation/index.d.ts.map +1 -1
- package/dist/notebook-creation/index.js +2 -0
- package/dist/notebook-creation/index.js.map +1 -1
- package/dist/notebook-creation/notebook-creator.d.ts +6 -82
- package/dist/notebook-creation/notebook-creator.d.ts.map +1 -1
- package/dist/notebook-creation/notebook-creator.js +49 -835
- package/dist/notebook-creation/notebook-creator.js.map +1 -1
- package/dist/notebook-creation/notebook-nav.d.ts +19 -0
- package/dist/notebook-creation/notebook-nav.d.ts.map +1 -0
- package/dist/notebook-creation/notebook-nav.js +239 -0
- package/dist/notebook-creation/notebook-nav.js.map +1 -0
- package/dist/notebook-creation/notebook-sync.d.ts.map +1 -1
- package/dist/notebook-creation/notebook-sync.js +36 -38
- package/dist/notebook-creation/notebook-sync.js.map +1 -1
- package/dist/notebook-creation/selector-discovery.d.ts.map +1 -1
- package/dist/notebook-creation/selector-discovery.js +17 -24
- package/dist/notebook-creation/selector-discovery.js.map +1 -1
- package/dist/notebook-creation/selectors.d.ts +23 -19
- package/dist/notebook-creation/selectors.d.ts.map +1 -1
- package/dist/notebook-creation/selectors.js +69 -33
- package/dist/notebook-creation/selectors.js.map +1 -1
- package/dist/notebook-creation/source-manager.d.ts +22 -0
- package/dist/notebook-creation/source-manager.d.ts.map +1 -1
- package/dist/notebook-creation/source-manager.js +716 -50
- package/dist/notebook-creation/source-manager.js.map +1 -1
- package/dist/notebook-creation/types.d.ts +4 -0
- package/dist/notebook-creation/types.d.ts.map +1 -1
- package/dist/notebook-creation/video-manager.d.ts.map +1 -1
- package/dist/notebook-creation/video-manager.js +33 -35
- package/dist/notebook-creation/video-manager.js.map +1 -1
- package/dist/observability/metrics.d.ts +19 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +35 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/quota/quota-manager.d.ts +11 -3
- package/dist/quota/quota-manager.d.ts.map +1 -1
- package/dist/quota/quota-manager.js +139 -47
- package/dist/quota/quota-manager.js.map +1 -1
- package/dist/resources/resource-handlers.d.ts.map +1 -1
- package/dist/resources/resource-handlers.js +29 -12
- package/dist/resources/resource-handlers.js.map +1 -1
- package/dist/session/browser-session.d.ts.map +1 -1
- package/dist/session/browser-session.js +22 -22
- package/dist/session/browser-session.js.map +1 -1
- package/dist/session/session-timeout.d.ts.map +1 -1
- package/dist/session/session-timeout.js +4 -2
- package/dist/session/session-timeout.js.map +1 -1
- package/dist/session/shared-context-manager.d.ts.map +1 -1
- package/dist/session/shared-context-manager.js +31 -30
- package/dist/session/shared-context-manager.js.map +1 -1
- package/dist/tools/annotations.js +9 -9
- package/dist/tools/annotations.js.map +1 -1
- package/dist/tools/definitions/ask-question.d.ts.map +1 -1
- package/dist/tools/definitions/ask-question.js +35 -100
- package/dist/tools/definitions/ask-question.js.map +1 -1
- package/dist/tools/definitions/chat-history.d.ts +47 -1
- package/dist/tools/definitions/chat-history.d.ts.map +1 -1
- package/dist/tools/definitions/chat-history.js +10 -1
- package/dist/tools/definitions/chat-history.js.map +1 -1
- package/dist/tools/definitions/data-tables.d.ts.map +1 -1
- package/dist/tools/definitions/data-tables.js +2 -0
- package/dist/tools/definitions/data-tables.js.map +1 -1
- package/dist/tools/definitions/gemini.d.ts.map +1 -1
- package/dist/tools/definitions/gemini.js +40 -10
- package/dist/tools/definitions/gemini.js.map +1 -1
- package/dist/tools/definitions/notebook-management.d.ts.map +1 -1
- package/dist/tools/definitions/notebook-management.js +100 -70
- package/dist/tools/definitions/notebook-management.js.map +1 -1
- package/dist/tools/definitions/query-history.d.ts +47 -1
- package/dist/tools/definitions/query-history.d.ts.map +1 -1
- package/dist/tools/definitions/query-history.js +7 -0
- package/dist/tools/definitions/query-history.js.map +1 -1
- package/dist/tools/definitions/session-management.d.ts.map +1 -1
- package/dist/tools/definitions/session-management.js +5 -0
- package/dist/tools/definitions/session-management.js.map +1 -1
- package/dist/tools/definitions/system.d.ts.map +1 -1
- package/dist/tools/definitions/system.js +71 -100
- package/dist/tools/definitions/system.js.map +1 -1
- package/dist/tools/definitions/video.d.ts.map +1 -1
- package/dist/tools/definitions/video.js +3 -0
- package/dist/tools/definitions/video.js.map +1 -1
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +4 -0
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/handlers/ask-question.d.ts +1 -1
- package/dist/tools/handlers/ask-question.d.ts.map +1 -1
- package/dist/tools/handlers/ask-question.js +56 -12
- package/dist/tools/handlers/ask-question.js.map +1 -1
- package/dist/tools/handlers/audio-video.d.ts.map +1 -1
- package/dist/tools/handlers/audio-video.js +15 -7
- package/dist/tools/handlers/audio-video.js.map +1 -1
- package/dist/tools/handlers/auth.d.ts +14 -19
- package/dist/tools/handlers/auth.d.ts.map +1 -1
- package/dist/tools/handlers/auth.js +77 -121
- package/dist/tools/handlers/auth.js.map +1 -1
- package/dist/tools/handlers/error-utils.d.ts +7 -0
- package/dist/tools/handlers/error-utils.d.ts.map +1 -0
- package/dist/tools/handlers/error-utils.js +17 -0
- package/dist/tools/handlers/error-utils.js.map +1 -0
- package/dist/tools/handlers/gemini.d.ts +1 -0
- package/dist/tools/handlers/gemini.d.ts.map +1 -1
- package/dist/tools/handlers/gemini.js +81 -51
- package/dist/tools/handlers/gemini.js.map +1 -1
- package/dist/tools/handlers/index.d.ts +39 -47
- package/dist/tools/handlers/index.d.ts.map +1 -1
- package/dist/tools/handlers/index.js +13 -2
- package/dist/tools/handlers/index.js.map +1 -1
- package/dist/tools/handlers/notebook-creation.d.ts.map +1 -1
- package/dist/tools/handlers/notebook-creation.js +99 -20
- package/dist/tools/handlers/notebook-creation.js.map +1 -1
- package/dist/tools/handlers/notebook-management.d.ts +8 -8
- package/dist/tools/handlers/notebook-management.d.ts.map +1 -1
- package/dist/tools/handlers/notebook-management.js +34 -80
- package/dist/tools/handlers/notebook-management.js.map +1 -1
- package/dist/tools/handlers/session-management.d.ts.map +1 -1
- package/dist/tools/handlers/session-management.js +12 -5
- package/dist/tools/handlers/session-management.js.map +1 -1
- package/dist/tools/handlers/system.d.ts.map +1 -1
- package/dist/tools/handlers/system.js +45 -10
- package/dist/tools/handlers/system.js.map +1 -1
- package/dist/tools/handlers/types.d.ts +1 -1
- package/dist/tools/handlers/types.d.ts.map +1 -1
- package/dist/tools/handlers/webhooks.d.ts.map +1 -1
- package/dist/tools/handlers/webhooks.js +15 -13
- package/dist/tools/handlers/webhooks.js.map +1 -1
- package/dist/types.d.ts +7 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/audit-logger.d.ts +19 -1
- package/dist/utils/audit-logger.d.ts.map +1 -1
- package/dist/utils/audit-logger.js +193 -27
- package/dist/utils/audit-logger.js.map +1 -1
- package/dist/utils/cleanup-manager.d.ts.map +1 -1
- package/dist/utils/cleanup-manager.js +6 -3
- package/dist/utils/cleanup-manager.js.map +1 -1
- package/dist/utils/crypto.d.ts +4 -1
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js +32 -21
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/file-lock.d.ts.map +1 -1
- package/dist/utils/file-lock.js +80 -16
- package/dist/utils/file-lock.js.map +1 -1
- package/dist/utils/file-permissions.d.ts +2 -0
- package/dist/utils/file-permissions.d.ts.map +1 -1
- package/dist/utils/file-permissions.js +2 -1
- package/dist/utils/file-permissions.js.map +1 -1
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +16 -0
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/page-utils.d.ts.map +1 -1
- package/dist/utils/page-utils.js +22 -39
- package/dist/utils/page-utils.js.map +1 -1
- package/dist/utils/response-validator.d.ts.map +1 -1
- package/dist/utils/response-validator.js +27 -22
- package/dist/utils/response-validator.js.map +1 -1
- package/dist/utils/secrets-scanner.d.ts +11 -0
- package/dist/utils/secrets-scanner.d.ts.map +1 -1
- package/dist/utils/secrets-scanner.js +63 -15
- package/dist/utils/secrets-scanner.js.map +1 -1
- package/dist/utils/secure-memory.d.ts +9 -31
- package/dist/utils/secure-memory.d.ts.map +1 -1
- package/dist/utils/secure-memory.js +17 -102
- package/dist/utils/secure-memory.js.map +1 -1
- package/dist/utils/security.d.ts +4 -3
- package/dist/utils/security.d.ts.map +1 -1
- package/dist/utils/security.js +41 -11
- package/dist/utils/security.js.map +1 -1
- package/dist/utils/stealth-utils.d.ts.map +1 -1
- package/dist/utils/stealth-utils.js +4 -4
- package/dist/utils/stealth-utils.js.map +1 -1
- package/dist/webhooks/types.d.ts +2 -0
- package/dist/webhooks/types.d.ts.map +1 -1
- package/dist/webhooks/webhook-dispatcher.d.ts +80 -12
- package/dist/webhooks/webhook-dispatcher.d.ts.map +1 -1
- package/dist/webhooks/webhook-dispatcher.js +472 -72
- package/dist/webhooks/webhook-dispatcher.js.map +1 -1
- package/docs/archive/ISSUES-legacy-2026-04-24.md +644 -0
- package/docs/dependency-risk.md +25 -0
- package/docs/testing-runbook.md +166 -0
- package/docs/usage-guide.md +2 -1
- package/package.json +32 -15
|
@@ -7,7 +7,9 @@ import * as fs from "fs";
|
|
|
7
7
|
import * as path from "path";
|
|
8
8
|
import { log } from "../utils/logger.js";
|
|
9
9
|
import { randomDelay, humanType } from "../utils/stealth-utils.js";
|
|
10
|
-
import {
|
|
10
|
+
import { findElement, getSelectors } from "./selectors.js";
|
|
11
|
+
const STANDARD_SOURCE_PROCESSING_TIMEOUT_MS = 30000;
|
|
12
|
+
const LENIENT_SOURCE_PROCESSING_TIMEOUT_MS = 60000;
|
|
11
13
|
export class SourceManager {
|
|
12
14
|
authManager;
|
|
13
15
|
contextManager;
|
|
@@ -42,6 +44,7 @@ export class SourceManager {
|
|
|
42
44
|
await page.waitForTimeout(2000);
|
|
43
45
|
// Extract source information from the page
|
|
44
46
|
const sources = await page.evaluate(() => {
|
|
47
|
+
const browser = globalThis;
|
|
45
48
|
const results = [];
|
|
46
49
|
// Look for source items in the sidebar/sources panel
|
|
47
50
|
// Common patterns in NotebookLM:
|
|
@@ -56,8 +59,7 @@ export class SourceManager {
|
|
|
56
59
|
'.sources-list > *',
|
|
57
60
|
];
|
|
58
61
|
for (const selector of sourceSelectors) {
|
|
59
|
-
|
|
60
|
-
const items = document.querySelectorAll(selector);
|
|
62
|
+
const items = Array.from(browser.document.querySelectorAll(selector));
|
|
61
63
|
for (let i = 0; i < items.length; i++) {
|
|
62
64
|
const item = items[i];
|
|
63
65
|
const text = item.textContent?.trim() || "";
|
|
@@ -125,27 +127,50 @@ export class SourceManager {
|
|
|
125
127
|
try {
|
|
126
128
|
await page.waitForLoadState('networkidle', { timeout: 15000 });
|
|
127
129
|
}
|
|
128
|
-
catch {
|
|
130
|
+
catch (err) {
|
|
131
|
+
log.debug(`source-manager: waiting for network idle after page load: ${err instanceof Error ? err.message : String(err)}`);
|
|
129
132
|
log.warning(" Network idle timeout, continuing...");
|
|
130
133
|
}
|
|
131
134
|
await randomDelay(3000, 4000);
|
|
132
135
|
// Check page state
|
|
133
136
|
const pageState = await page.evaluate(() => {
|
|
134
|
-
|
|
135
|
-
const addSourceBtn = document.querySelector('button[aria-label="Add source"]');
|
|
136
|
-
|
|
137
|
-
return { hasAddSourceBtn: !!addSourceBtn, windowWidth: window.innerWidth };
|
|
137
|
+
const browser = globalThis;
|
|
138
|
+
const addSourceBtn = browser.document.querySelector('button[aria-label="Add source"]');
|
|
139
|
+
return { hasAddSourceBtn: !!addSourceBtn, windowWidth: browser.window.innerWidth };
|
|
138
140
|
});
|
|
139
141
|
log.dim(` Page state: width=${pageState.windowWidth}, addSourceBtn=${pageState.hasAddSourceBtn}`);
|
|
140
142
|
// Check if source dialog is already open (new/empty notebooks may auto-open it)
|
|
141
143
|
const dialogAlreadyOpen = await page.evaluate(() => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const dropzone = document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const sourceOptions = document.querySelector('[aria-label="Upload sources from your computer"]');
|
|
148
|
-
|
|
144
|
+
const browser = globalThis;
|
|
145
|
+
const asElements = (selector) => Array.from(browser.document.querySelectorAll(selector));
|
|
146
|
+
const dropzone = browser.document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
|
|
147
|
+
if (dropzone && dropzone.offsetParent !== null)
|
|
148
|
+
return true;
|
|
149
|
+
const sourceOptions = browser.document.querySelector('[aria-label="Upload sources from your computer"]');
|
|
150
|
+
if (sourceOptions && sourceOptions.offsetParent !== null)
|
|
151
|
+
return true;
|
|
152
|
+
const textArea = browser.document.querySelector('textarea.text-area, textarea[class*="text-area"], textarea.mat-mdc-form-field-textarea-control');
|
|
153
|
+
if (textArea && textArea.offsetParent !== null)
|
|
154
|
+
return true;
|
|
155
|
+
const chips = asElements("mat-chip, mat-chip-option, [mat-chip-option], button, span");
|
|
156
|
+
for (const chip of chips) {
|
|
157
|
+
if (chip.offsetParent === null)
|
|
158
|
+
continue;
|
|
159
|
+
const text = chip.textContent?.trim() || "";
|
|
160
|
+
if (text.includes("Copied text") || text.includes("Website") || text.includes("Upload")) {
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const dialogs = asElements("mat-dialog-container, [role=\"dialog\"], .cdk-overlay-pane");
|
|
165
|
+
for (const dialog of dialogs) {
|
|
166
|
+
if (dialog.offsetParent === null)
|
|
167
|
+
continue;
|
|
168
|
+
const text = dialog.textContent?.trim() || "";
|
|
169
|
+
if (text.includes("Copied text") || text.includes("Website") || text.includes("Upload")) {
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
149
174
|
});
|
|
150
175
|
if (dialogAlreadyOpen) {
|
|
151
176
|
log.info(" 📋 Source dialog already open");
|
|
@@ -184,10 +209,9 @@ export class SourceManager {
|
|
|
184
209
|
await randomDelay(1500, 2000);
|
|
185
210
|
// Verify dialog opened
|
|
186
211
|
const dialogOpened = await page.evaluate(() => {
|
|
187
|
-
|
|
188
|
-
const dropzone = document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
|
|
189
|
-
|
|
190
|
-
const sourceOptions = document.querySelector('[aria-label="Upload sources from your computer"]');
|
|
212
|
+
const browser = globalThis;
|
|
213
|
+
const dropzone = browser.document.querySelector('.dropzone__file-dialog-button, span[xapscottyuploadertrigger]');
|
|
214
|
+
const sourceOptions = browser.document.querySelector('[aria-label="Upload sources from your computer"]');
|
|
191
215
|
return !!(dropzone || sourceOptions);
|
|
192
216
|
});
|
|
193
217
|
if (!dialogOpened) {
|
|
@@ -248,14 +272,14 @@ export class SourceManager {
|
|
|
248
272
|
const sourceIndex = parseInt(indexMatch[1], 10);
|
|
249
273
|
// Find and click on the source to select it
|
|
250
274
|
const clicked = await page.evaluate((index) => {
|
|
275
|
+
const browser = globalThis;
|
|
251
276
|
const sourceSelectors = [
|
|
252
277
|
'mat-list-item',
|
|
253
278
|
'[class*="source-item"]',
|
|
254
279
|
'[role="listitem"]',
|
|
255
280
|
];
|
|
256
281
|
for (const selector of sourceSelectors) {
|
|
257
|
-
|
|
258
|
-
const items = document.querySelectorAll(selector);
|
|
282
|
+
const items = Array.from(browser.document.querySelectorAll(selector));
|
|
259
283
|
if (items.length > index) {
|
|
260
284
|
// Look for delete button within the item or select it
|
|
261
285
|
const item = items[index];
|
|
@@ -279,6 +303,7 @@ export class SourceManager {
|
|
|
279
303
|
// Look for delete button in toolbar or context menu
|
|
280
304
|
await randomDelay(500, 800);
|
|
281
305
|
const deleted = await page.evaluate(() => {
|
|
306
|
+
const browser = globalThis;
|
|
282
307
|
// Look for delete button that appeared after selection
|
|
283
308
|
const deleteSelectors = [
|
|
284
309
|
'button[aria-label*="delete" i]',
|
|
@@ -287,8 +312,7 @@ export class SourceManager {
|
|
|
287
312
|
'[class*="trash"]',
|
|
288
313
|
];
|
|
289
314
|
for (const selector of deleteSelectors) {
|
|
290
|
-
|
|
291
|
-
const btn = document.querySelector(selector);
|
|
315
|
+
const btn = browser.document.querySelector(selector);
|
|
292
316
|
if (btn && btn.offsetParent !== null) {
|
|
293
317
|
btn.click();
|
|
294
318
|
return true;
|
|
@@ -303,9 +327,9 @@ export class SourceManager {
|
|
|
303
327
|
// Confirm deletion if dialog appears
|
|
304
328
|
await randomDelay(500, 800);
|
|
305
329
|
await page.evaluate(() => {
|
|
330
|
+
const browser = globalThis;
|
|
306
331
|
// Look for confirm button in dialog
|
|
307
|
-
|
|
308
|
-
const buttons = document.querySelectorAll("button");
|
|
332
|
+
const buttons = Array.from(browser.document.querySelectorAll("button"));
|
|
309
333
|
for (const btn of buttons) {
|
|
310
334
|
const text = btn.textContent?.toLowerCase() || "";
|
|
311
335
|
if (text.includes("delete") || text.includes("remove") || text.includes("confirm")) {
|
|
@@ -340,16 +364,15 @@ export class SourceManager {
|
|
|
340
364
|
async addUrlSourceInternal(page, url) {
|
|
341
365
|
// Click URL/Website source type option — prefer locale-independent signals
|
|
342
366
|
const urlOptionClicked = await page.evaluate(() => {
|
|
367
|
+
const browser = globalThis;
|
|
343
368
|
// Primary: data/value attribute (locale-independent)
|
|
344
|
-
|
|
345
|
-
const byData = document.querySelector('[data-source-type="url"], [value="url"], mat-chip[value="url"]');
|
|
369
|
+
const byData = browser.document.querySelector('[data-source-type="url"], [value="url"], mat-chip[value="url"]');
|
|
346
370
|
if (byData) {
|
|
347
371
|
byData.click();
|
|
348
372
|
return true;
|
|
349
373
|
}
|
|
350
374
|
// Fallback: text/aria match ("URL" is the same word in most languages)
|
|
351
|
-
|
|
352
|
-
const buttons = document.querySelectorAll("button, [role='button'], mat-chip");
|
|
375
|
+
const buttons = Array.from(browser.document.querySelectorAll("button, [role='button'], mat-chip"));
|
|
353
376
|
for (const btn of buttons) {
|
|
354
377
|
const text = btn.textContent?.toLowerCase() || "";
|
|
355
378
|
const aria = btn.getAttribute("aria-label")?.toLowerCase() || "";
|
|
@@ -381,13 +404,17 @@ export class SourceManager {
|
|
|
381
404
|
async addTextSourceInternal(page, text, _title) {
|
|
382
405
|
// Click text/paste option
|
|
383
406
|
const textOptionClicked = await page.evaluate(() => {
|
|
384
|
-
|
|
385
|
-
const buttons = document.querySelectorAll("button, [role='button'], mat-chip");
|
|
407
|
+
const browser = globalThis;
|
|
408
|
+
const buttons = Array.from(browser.document.querySelectorAll("button, [role='button'], mat-chip, mat-chip-option, [mat-chip-option]"));
|
|
386
409
|
for (const btn of buttons) {
|
|
387
410
|
const btnText = btn.textContent?.toLowerCase() || "";
|
|
388
411
|
const aria = btn.getAttribute("aria-label")?.toLowerCase() || "";
|
|
389
|
-
|
|
390
|
-
|
|
412
|
+
const combined = `${btnText} ${aria}`;
|
|
413
|
+
if (combined.includes("search the web") || combined.includes("discover sources")) {
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (btnText.includes("copied text") || btnText.includes("paste as text") ||
|
|
417
|
+
aria.includes("copied") || aria.includes("paste as text")) {
|
|
391
418
|
btn.click();
|
|
392
419
|
return true;
|
|
393
420
|
}
|
|
@@ -398,16 +425,19 @@ export class SourceManager {
|
|
|
398
425
|
throw new Error("Could not find text/paste source option");
|
|
399
426
|
}
|
|
400
427
|
await randomDelay(800, 1200);
|
|
401
|
-
|
|
402
|
-
|
|
428
|
+
const activeSelector = await this.findValidTextInputSelectorOnPage(page);
|
|
429
|
+
if (!activeSelector) {
|
|
430
|
+
throw new Error("Could not find text input area");
|
|
431
|
+
}
|
|
432
|
+
const textArea = await page.$(activeSelector);
|
|
403
433
|
if (!textArea) {
|
|
404
434
|
throw new Error("Could not find text input area");
|
|
405
435
|
}
|
|
406
436
|
// Use clipboard for large text
|
|
407
437
|
if (text.length > 500) {
|
|
408
438
|
await page.evaluate((t) => {
|
|
409
|
-
|
|
410
|
-
navigator.clipboard.writeText(t);
|
|
439
|
+
const browser = globalThis;
|
|
440
|
+
browser.navigator.clipboard.writeText(t);
|
|
411
441
|
}, text);
|
|
412
442
|
await textArea.focus();
|
|
413
443
|
await page.keyboard.down("Control");
|
|
@@ -415,28 +445,26 @@ export class SourceManager {
|
|
|
415
445
|
await page.keyboard.up("Control");
|
|
416
446
|
}
|
|
417
447
|
else {
|
|
418
|
-
await humanType(page,
|
|
448
|
+
await humanType(page, activeSelector, text);
|
|
419
449
|
}
|
|
420
450
|
await randomDelay(500, 800);
|
|
421
451
|
// Click Insert button — prefer locale-independent selectors
|
|
422
452
|
const insertClicked = await page.evaluate(() => {
|
|
453
|
+
const browser = globalThis;
|
|
423
454
|
// Primary: type=submit (locale-independent)
|
|
424
|
-
|
|
425
|
-
const submitBtn = document.querySelector("button[type='submit']:not([disabled])");
|
|
455
|
+
const submitBtn = browser.document.querySelector("button[type='submit']:not([disabled])");
|
|
426
456
|
if (submitBtn && submitBtn.offsetParent !== null) {
|
|
427
457
|
submitBtn.click();
|
|
428
458
|
return true;
|
|
429
459
|
}
|
|
430
460
|
// Secondary: primary color class (locale-independent NotebookLM convention)
|
|
431
|
-
|
|
432
|
-
const primaryBtn = document.querySelector("button.button-color--primary:not([disabled])");
|
|
461
|
+
const primaryBtn = browser.document.querySelector("button.button-color--primary:not([disabled])");
|
|
433
462
|
if (primaryBtn && primaryBtn.offsetParent !== null) {
|
|
434
463
|
primaryBtn.click();
|
|
435
464
|
return true;
|
|
436
465
|
}
|
|
437
466
|
// Fallback: text match (English only)
|
|
438
|
-
|
|
439
|
-
const buttons = document.querySelectorAll("button");
|
|
467
|
+
const buttons = Array.from(browser.document.querySelectorAll("button"));
|
|
440
468
|
for (const btn of buttons) {
|
|
441
469
|
const text = btn.textContent?.toLowerCase() || "";
|
|
442
470
|
if (text.includes("insert") || text.includes("add") || text.includes("submit")) {
|
|
@@ -450,6 +478,35 @@ export class SourceManager {
|
|
|
450
478
|
throw new Error("Could not find Insert button");
|
|
451
479
|
}
|
|
452
480
|
}
|
|
481
|
+
async findValidTextInputSelectorOnPage(page) {
|
|
482
|
+
for (const selector of getSelectors("textInput")) {
|
|
483
|
+
try {
|
|
484
|
+
const state = await page.evaluate((targetSelector) => {
|
|
485
|
+
const browser = globalThis;
|
|
486
|
+
const textarea = browser.document.querySelector(targetSelector);
|
|
487
|
+
if (!textarea || textarea.offsetParent === null) {
|
|
488
|
+
return { matches: false };
|
|
489
|
+
}
|
|
490
|
+
const aria = textarea.getAttribute("aria-label")?.toLowerCase() || "";
|
|
491
|
+
const placeholder = textarea.getAttribute("placeholder")?.toLowerCase() || "";
|
|
492
|
+
const className = textarea.className?.toLowerCase() || "";
|
|
493
|
+
const haystack = `${aria} ${placeholder} ${className}`;
|
|
494
|
+
return {
|
|
495
|
+
matches: !haystack.includes("discover sources") &&
|
|
496
|
+
!haystack.includes("search the web") &&
|
|
497
|
+
!haystack.includes("query-box"),
|
|
498
|
+
};
|
|
499
|
+
}, selector);
|
|
500
|
+
if (state.matches) {
|
|
501
|
+
return selector;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch (err) {
|
|
505
|
+
log.debug(`source-manager: validating standalone text source input selector: ${err instanceof Error ? err.message : String(err)}`);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
453
510
|
/**
|
|
454
511
|
* Internal: Add file source
|
|
455
512
|
* December 2025: NotebookLM creates a hidden input[type="file"] AFTER clicking
|
|
@@ -550,15 +607,14 @@ export class SourceManager {
|
|
|
550
607
|
/**
|
|
551
608
|
* Wait for source processing to complete
|
|
552
609
|
*/
|
|
553
|
-
async waitForSourceProcessing(page, timeoutMs =
|
|
610
|
+
async waitForSourceProcessing(page, timeoutMs = STANDARD_SOURCE_PROCESSING_TIMEOUT_MS) {
|
|
554
611
|
const startTime = Date.now();
|
|
555
612
|
while (Date.now() - startTime < timeoutMs) {
|
|
556
613
|
const isProcessing = await page.evaluate(() => {
|
|
614
|
+
const browser = globalThis;
|
|
557
615
|
// Look for processing indicators
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
// @ts-expect-error - DOM types
|
|
561
|
-
const spinner = document.querySelector('[class*="spinner"], [class*="loading"]');
|
|
616
|
+
const progressBar = browser.document.querySelector('[role="progressbar"]');
|
|
617
|
+
const spinner = browser.document.querySelector('[class*="spinner"], [class*="loading"]');
|
|
562
618
|
return !!(progressBar || spinner);
|
|
563
619
|
});
|
|
564
620
|
if (!isProcessing) {
|
|
@@ -576,11 +632,621 @@ export class SourceManager {
|
|
|
576
632
|
try {
|
|
577
633
|
await this.page.close();
|
|
578
634
|
}
|
|
579
|
-
catch {
|
|
635
|
+
catch (err) {
|
|
636
|
+
log.debug(`source-manager: closing page: ${err instanceof Error ? err.message : String(err)}`);
|
|
580
637
|
// Ignore close errors
|
|
581
638
|
}
|
|
582
639
|
this.page = null;
|
|
583
640
|
}
|
|
584
641
|
}
|
|
585
642
|
}
|
|
643
|
+
export class NotebookCreationSourceManager {
|
|
644
|
+
getPage;
|
|
645
|
+
constructor(getPage) {
|
|
646
|
+
this.getPage = getPage;
|
|
647
|
+
}
|
|
648
|
+
async addSource(source) {
|
|
649
|
+
const page = this.requirePage();
|
|
650
|
+
const expectedNotebookUrl = page.url();
|
|
651
|
+
log.info(`📍 Current notebook URL: ${expectedNotebookUrl}`);
|
|
652
|
+
const dialogAlreadyOpen = await this.isSourceDialogOpen();
|
|
653
|
+
log.info(`📋 Source dialog already open: ${dialogAlreadyOpen}`);
|
|
654
|
+
if (!dialogAlreadyOpen) {
|
|
655
|
+
await this.clickAddSource();
|
|
656
|
+
const currentUrl = page.url();
|
|
657
|
+
if (!currentUrl.includes("/notebook/") ||
|
|
658
|
+
(expectedNotebookUrl.includes("/notebook/") &&
|
|
659
|
+
!currentUrl.includes(expectedNotebookUrl.split("/notebook/")[1]?.split("?")[0] || ""))) {
|
|
660
|
+
log.error(`❌ URL changed unexpectedly! Expected: ${expectedNotebookUrl}, Got: ${currentUrl}`);
|
|
661
|
+
throw new Error("Navigation error: accidentally navigated away from notebook. This may indicate clicking wrong button.");
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
log.info("📋 Source dialog already open - skipping clickAddSource");
|
|
666
|
+
}
|
|
667
|
+
switch (source.type) {
|
|
668
|
+
case "url":
|
|
669
|
+
await this.addUrlSource(source.value);
|
|
670
|
+
break;
|
|
671
|
+
case "text":
|
|
672
|
+
await this.addTextSource(source.value, source.title);
|
|
673
|
+
break;
|
|
674
|
+
case "file":
|
|
675
|
+
await this.addFileSource(source.value);
|
|
676
|
+
break;
|
|
677
|
+
default:
|
|
678
|
+
throw new Error(`Unknown source type: ${source.type}`);
|
|
679
|
+
}
|
|
680
|
+
log.info(`📍 URL after adding source: ${page.url()}`);
|
|
681
|
+
}
|
|
682
|
+
getSourceDescription(source) {
|
|
683
|
+
switch (source.type) {
|
|
684
|
+
case "url":
|
|
685
|
+
try {
|
|
686
|
+
const url = new URL(source.value);
|
|
687
|
+
return `URL: ${url.hostname}`;
|
|
688
|
+
}
|
|
689
|
+
catch (err) {
|
|
690
|
+
log.debug(`source-manager: parsing source URL in getSourceDescription: ${err instanceof Error ? err.message : String(err)}`);
|
|
691
|
+
return `URL: ${source.value.slice(0, 50)}`;
|
|
692
|
+
}
|
|
693
|
+
case "text":
|
|
694
|
+
return source.title || `Text: ${source.value.slice(0, 30)}...`;
|
|
695
|
+
case "file":
|
|
696
|
+
return `File: ${path.basename(source.value)}`;
|
|
697
|
+
default:
|
|
698
|
+
return "Unknown source";
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
requirePage() {
|
|
702
|
+
const page = this.getPage();
|
|
703
|
+
if (!page)
|
|
704
|
+
throw new Error("Page not initialized");
|
|
705
|
+
return page;
|
|
706
|
+
}
|
|
707
|
+
async isSourceDialogOpen() {
|
|
708
|
+
const page = this.getPage();
|
|
709
|
+
if (!page)
|
|
710
|
+
return false;
|
|
711
|
+
const dialogIndicators = await page.evaluate(() => {
|
|
712
|
+
const browser = globalThis;
|
|
713
|
+
const dialogs = Array.from(browser.document.querySelectorAll("mat-dialog-container"));
|
|
714
|
+
for (const d of dialogs) {
|
|
715
|
+
if (d.offsetParent !== null && d.textContent?.trim()) {
|
|
716
|
+
return { open: true, reason: "dialog_container_visible" };
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const chipGroup = browser.document.querySelector("mat-chip-listbox, mat-chip-group, mat-chip-set");
|
|
720
|
+
if (chipGroup && chipGroup.offsetParent !== null) {
|
|
721
|
+
return { open: true, reason: "chip_group_visible" };
|
|
722
|
+
}
|
|
723
|
+
const dropzones = Array.from(browser.document.querySelectorAll('.dropzone, [class*="dropzone"]'));
|
|
724
|
+
if (dropzones.length > 0) {
|
|
725
|
+
for (const dz of dropzones) {
|
|
726
|
+
if (dz.offsetParent !== null) {
|
|
727
|
+
return { open: true, reason: "dropzone_visible" };
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const uploadBtn = browser.document.querySelector('button[class*="upload"], input[type="file"]');
|
|
732
|
+
if (uploadBtn && uploadBtn.offsetParent !== null) {
|
|
733
|
+
return { open: true, reason: "upload_button_visible" };
|
|
734
|
+
}
|
|
735
|
+
return { open: false };
|
|
736
|
+
});
|
|
737
|
+
log.info(`📋 isSourceDialogOpen check: ${JSON.stringify(dialogIndicators)}`);
|
|
738
|
+
return dialogIndicators.open;
|
|
739
|
+
}
|
|
740
|
+
async clickAddSource() {
|
|
741
|
+
const page = this.requirePage();
|
|
742
|
+
log.info("📎 Clicking 'Add source' button...");
|
|
743
|
+
const debugEnabled = process.env.DEBUG === "true";
|
|
744
|
+
if (debugEnabled) {
|
|
745
|
+
log.dim(` Current URL: ${page.url()}`);
|
|
746
|
+
}
|
|
747
|
+
await randomDelay(2000, 3000);
|
|
748
|
+
if (debugEnabled) {
|
|
749
|
+
const buttonsInfo = await page.evaluate(() => {
|
|
750
|
+
const browser = globalThis;
|
|
751
|
+
const buttons = Array.from(browser.document.querySelectorAll('button, [role="button"]'));
|
|
752
|
+
const info = [];
|
|
753
|
+
for (const btn of buttons) {
|
|
754
|
+
const text = btn.textContent?.trim().substring(0, 50) || "";
|
|
755
|
+
const aria = btn.getAttribute("aria-label") || "";
|
|
756
|
+
const cls = btn.className?.substring(0, 50) || "";
|
|
757
|
+
const visible = btn.offsetParent !== null;
|
|
758
|
+
if (aria.toLowerCase().includes("add") || aria.toLowerCase().includes("create") ||
|
|
759
|
+
text.toLowerCase().includes("add") || text.toLowerCase().includes("create") ||
|
|
760
|
+
cls.toLowerCase().includes("add") || cls.toLowerCase().includes("create")) {
|
|
761
|
+
info.push({ text, aria, class: cls, visible });
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return info;
|
|
765
|
+
});
|
|
766
|
+
log.dim(` Buttons found: ${JSON.stringify(buttonsInfo, null, 2)}`);
|
|
767
|
+
}
|
|
768
|
+
try {
|
|
769
|
+
let addSourceLocator = page.locator('button[aria-label="Add source"]');
|
|
770
|
+
let count = await addSourceLocator.count();
|
|
771
|
+
log.info(` Method 1a: Found ${count} button(s) with aria-label="Add source"`);
|
|
772
|
+
if (count === 0) {
|
|
773
|
+
addSourceLocator = page.locator('button[aria-label="Add sources"]');
|
|
774
|
+
count = await addSourceLocator.count();
|
|
775
|
+
log.info(` Method 1b: Found ${count} button(s) with aria-label="Add sources"`);
|
|
776
|
+
}
|
|
777
|
+
if (count > 0 && await addSourceLocator.first().isVisible()) {
|
|
778
|
+
await addSourceLocator.first().click();
|
|
779
|
+
await randomDelay(800, 1500);
|
|
780
|
+
log.success("✅ Clicked 'Add source' button (locator)");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
catch (e) {
|
|
785
|
+
log.info(` Locator approach failed: ${e}`);
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
const classLocator = page.locator('button.add-source-button');
|
|
789
|
+
const count = await classLocator.count();
|
|
790
|
+
log.info(` Method 2: Found ${count} button(s) with class add-source-button`);
|
|
791
|
+
if (count > 0 && await classLocator.first().isVisible()) {
|
|
792
|
+
await classLocator.first().click();
|
|
793
|
+
await randomDelay(800, 1500);
|
|
794
|
+
log.success("✅ Clicked 'Add source' button (class)");
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
catch (e) {
|
|
799
|
+
log.info(` Class selector failed: ${e}`);
|
|
800
|
+
}
|
|
801
|
+
try {
|
|
802
|
+
const clicked = await page.evaluate(() => {
|
|
803
|
+
const browser = globalThis;
|
|
804
|
+
const elements = Array.from(browser.document.querySelectorAll('button, [role="button"]'));
|
|
805
|
+
for (const el of elements) {
|
|
806
|
+
const elText = el.textContent?.trim().toLowerCase() || "";
|
|
807
|
+
const ariaLabel = el.getAttribute("aria-label")?.toLowerCase() || "";
|
|
808
|
+
const className = el.className?.toLowerCase() || "";
|
|
809
|
+
if (ariaLabel.includes("create") || className.includes("create-notebook") ||
|
|
810
|
+
elText.includes("create") || elText.includes("add note") ||
|
|
811
|
+
className.includes("add-note")) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
if (ariaLabel === "add source" || ariaLabel.includes("add source") ||
|
|
815
|
+
elText.includes("add source") || className.includes("add-source")) {
|
|
816
|
+
el.click();
|
|
817
|
+
return { clicked: true, aria: ariaLabel, text: elText.substring(0, 30) };
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return { clicked: false };
|
|
821
|
+
});
|
|
822
|
+
if (clicked.clicked) {
|
|
823
|
+
await randomDelay(800, 1500);
|
|
824
|
+
if (process.env.DEBUG === "true") {
|
|
825
|
+
log.debug(`source-manager: clicked add-source JS fallback (aria/text lengths: ${String(clicked.aria ?? "").length}/${String(clicked.text ?? "").length})`);
|
|
826
|
+
}
|
|
827
|
+
log.success("✅ Clicked 'Add source' button (JS fallback)");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
log.debug(`source-manager: clicking 'Add source' button via JS fallback: ${err instanceof Error ? err.message : String(err)}`);
|
|
833
|
+
}
|
|
834
|
+
log.warning("⚠️ Add source button not found, waiting and retrying...");
|
|
835
|
+
await randomDelay(3000, 4000);
|
|
836
|
+
try {
|
|
837
|
+
let addSourceLocator = page.locator('button[aria-label="Add source"]');
|
|
838
|
+
let count = await addSourceLocator.count();
|
|
839
|
+
log.info(` Retry: Found ${count} button(s) with aria-label="Add source"`);
|
|
840
|
+
if (count === 0) {
|
|
841
|
+
addSourceLocator = page.locator('button[aria-label="Add sources"]');
|
|
842
|
+
count = await addSourceLocator.count();
|
|
843
|
+
log.info(` Retry: Found ${count} button(s) with aria-label="Add sources"`);
|
|
844
|
+
}
|
|
845
|
+
if (count > 0 && await addSourceLocator.first().isVisible()) {
|
|
846
|
+
await addSourceLocator.first().click();
|
|
847
|
+
await randomDelay(800, 1500);
|
|
848
|
+
log.success("✅ Clicked 'Add source' button (retry)");
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
catch (e) {
|
|
853
|
+
log.info(` Retry failed: ${e}`);
|
|
854
|
+
}
|
|
855
|
+
throw new Error("Could not find 'Add source' button after retry");
|
|
856
|
+
}
|
|
857
|
+
async addUrlSource(url) {
|
|
858
|
+
const page = this.requirePage();
|
|
859
|
+
log.info(`🔗 Adding URL source: ${url}`);
|
|
860
|
+
await this.clickSourceTypeByText(["Website", "webWebsite", "Link", "Discover sources"]);
|
|
861
|
+
await randomDelay(500, 1000);
|
|
862
|
+
const selectors = getSelectors("urlInput");
|
|
863
|
+
for (const selector of selectors) {
|
|
864
|
+
try {
|
|
865
|
+
const input = await page.$(selector);
|
|
866
|
+
if (input && await input.isVisible()) {
|
|
867
|
+
await humanType(page, selector, url, { withTypos: false });
|
|
868
|
+
await randomDelay(500, 1000);
|
|
869
|
+
await this.clickSubmitButton();
|
|
870
|
+
await this.waitForSourceProcessing({ mode: "strict" });
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
catch (err) {
|
|
875
|
+
log.debug(`source-manager: entering URL into source input selector: ${err instanceof Error ? err.message : String(err)}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
throw new Error("Could not find URL input field");
|
|
879
|
+
}
|
|
880
|
+
async addTextSource(text, title) {
|
|
881
|
+
const page = this.requirePage();
|
|
882
|
+
log.info(`📝 Adding text source${title ? `: ${title}` : ""}`);
|
|
883
|
+
let clickedBySelector = false;
|
|
884
|
+
for (const selector of getSelectors("textSourceOption")) {
|
|
885
|
+
try {
|
|
886
|
+
const option = await page.$(selector);
|
|
887
|
+
if (option && await option.isVisible()) {
|
|
888
|
+
await option.click();
|
|
889
|
+
clickedBySelector = true;
|
|
890
|
+
log.success(`✅ Clicked text source option via selector: ${selector}`);
|
|
891
|
+
break;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
catch (err) {
|
|
895
|
+
log.debug(`source-manager: clicking text source option selector: ${err instanceof Error ? err.message : String(err)}`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (!clickedBySelector) {
|
|
899
|
+
await this.clickSourceTypeByText(["Copied text", "Paste as text", "Paste"]);
|
|
900
|
+
}
|
|
901
|
+
await randomDelay(2000, 2500);
|
|
902
|
+
const activeSelector = await this.findValidTextInputSelector(page);
|
|
903
|
+
const textarea = activeSelector ? await page.$(activeSelector) : null;
|
|
904
|
+
if (!textarea || !activeSelector) {
|
|
905
|
+
const visibleOptions = await this.getVisibleSourceOptions(page);
|
|
906
|
+
log.warning(`⚠️ Visible source options after text-source click: ${JSON.stringify(visibleOptions)}`);
|
|
907
|
+
const visibleTextareas = await this.getVisibleTextareas(page);
|
|
908
|
+
log.warning(`⚠️ Visible textareas after text-source click: ${JSON.stringify(visibleTextareas)}`);
|
|
909
|
+
throw new Error("Could not find text input area");
|
|
910
|
+
}
|
|
911
|
+
const isVisible = await textarea.isVisible().catch(() => false);
|
|
912
|
+
if (!isVisible) {
|
|
913
|
+
await randomDelay(1000, 1500);
|
|
914
|
+
}
|
|
915
|
+
await textarea.click();
|
|
916
|
+
await randomDelay(200, 400);
|
|
917
|
+
if (text.length > 500) {
|
|
918
|
+
await page.evaluate((clipboardText) => {
|
|
919
|
+
const browser = globalThis;
|
|
920
|
+
browser.navigator.clipboard.writeText(clipboardText);
|
|
921
|
+
}, text);
|
|
922
|
+
await page.keyboard.press("Control+V");
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
await humanType(page, activeSelector, text, { withTypos: false });
|
|
926
|
+
}
|
|
927
|
+
await page.evaluate(({ selector, value }) => {
|
|
928
|
+
const browser = globalThis;
|
|
929
|
+
const textarea = browser.document.querySelector(selector);
|
|
930
|
+
if (!textarea)
|
|
931
|
+
return false;
|
|
932
|
+
textarea.focus();
|
|
933
|
+
if (typeof textarea.value === "string") {
|
|
934
|
+
textarea.value = value;
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
textarea.textContent = value;
|
|
938
|
+
}
|
|
939
|
+
textarea.dispatchEvent(new browser.Event("input", { bubbles: true }));
|
|
940
|
+
textarea.dispatchEvent(new browser.Event("change", { bubbles: true }));
|
|
941
|
+
return true;
|
|
942
|
+
}, { selector: activeSelector, value: text });
|
|
943
|
+
await randomDelay(500, 1000);
|
|
944
|
+
const textState = await page.evaluate(({ selector }) => {
|
|
945
|
+
const browser = globalThis;
|
|
946
|
+
const textarea = browser.document.querySelector(selector);
|
|
947
|
+
const buttons = Array.from(browser.document.querySelectorAll("button")).map((button) => ({
|
|
948
|
+
text: button.textContent?.trim() || "",
|
|
949
|
+
disabled: button.getAttribute("disabled") !== null || button.getAttribute("aria-disabled") === "true",
|
|
950
|
+
}));
|
|
951
|
+
return {
|
|
952
|
+
textLength: typeof textarea?.value === "string"
|
|
953
|
+
? textarea.value.length
|
|
954
|
+
: (textarea?.textContent?.length || 0),
|
|
955
|
+
textareaClass: textarea?.className || "",
|
|
956
|
+
textareaAria: textarea?.getAttribute?.("aria-label") || "",
|
|
957
|
+
textareaPlaceholder: textarea?.getAttribute?.("placeholder") || "",
|
|
958
|
+
visibleDialogs: Array.from(browser.document.querySelectorAll("[role='dialog'], mat-dialog-container, .cdk-overlay-pane"))
|
|
959
|
+
.filter((dialog) => dialog.offsetParent !== null)
|
|
960
|
+
.map((dialog) => (dialog.textContent || "").trim().slice(0, 200))
|
|
961
|
+
.filter((text) => text.length > 0)
|
|
962
|
+
.slice(0, 4),
|
|
963
|
+
buttons: buttons.filter((button) => button.text.length > 0).slice(0, 12),
|
|
964
|
+
};
|
|
965
|
+
}, { selector: activeSelector });
|
|
966
|
+
log.info(`🧪 Text source state before insert: ${JSON.stringify(textState)}`);
|
|
967
|
+
await this.clickInsertButton();
|
|
968
|
+
await this.waitForSourceProcessing({ mode: "lenient" });
|
|
969
|
+
}
|
|
970
|
+
async addFileSource(filePath) {
|
|
971
|
+
const page = this.requirePage();
|
|
972
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
973
|
+
if (!fs.existsSync(absolutePath)) {
|
|
974
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
975
|
+
}
|
|
976
|
+
log.info(`📁 Adding file source: ${path.basename(absolutePath)}`);
|
|
977
|
+
await randomDelay(500, 1000);
|
|
978
|
+
try {
|
|
979
|
+
const chooseFileLocator = page.locator('span.dropzone__file-dialog-button');
|
|
980
|
+
if (await chooseFileLocator.count() > 0 && await chooseFileLocator.first().isVisible()) {
|
|
981
|
+
await chooseFileLocator.first().click();
|
|
982
|
+
await page.waitForSelector('input[type="file"]', { timeout: 5000, state: 'attached' });
|
|
983
|
+
await randomDelay(200, 400);
|
|
984
|
+
await page.locator('input[type="file"]').first().setInputFiles(absolutePath);
|
|
985
|
+
log.success(" ✅ File uploaded via Playwright click + setInputFiles");
|
|
986
|
+
await randomDelay(1000, 2000);
|
|
987
|
+
await this.waitForSourceProcessing({ mode: "lenient" });
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const xapscottyLocator = page.locator('[xapscottyuploadertrigger]');
|
|
991
|
+
if (await xapscottyLocator.count() > 0 && await xapscottyLocator.first().isVisible()) {
|
|
992
|
+
await xapscottyLocator.first().click();
|
|
993
|
+
await page.waitForSelector('input[type="file"]', { timeout: 5000, state: 'attached' });
|
|
994
|
+
await randomDelay(200, 400);
|
|
995
|
+
await page.locator('input[type="file"]').first().setInputFiles(absolutePath);
|
|
996
|
+
log.success(" ✅ File uploaded via xapscotty click + setInputFiles");
|
|
997
|
+
await randomDelay(1000, 2000);
|
|
998
|
+
await this.waitForSourceProcessing({ mode: "lenient" });
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
const uploadBtnLocator = page.locator('button[class*="upload"], button[aria-label="Upload sources from your computer"]');
|
|
1002
|
+
if (await uploadBtnLocator.count() > 0 && await uploadBtnLocator.first().isVisible()) {
|
|
1003
|
+
await uploadBtnLocator.first().click();
|
|
1004
|
+
await page.waitForSelector('input[type="file"]', { timeout: 5000, state: 'attached' });
|
|
1005
|
+
await randomDelay(200, 400);
|
|
1006
|
+
await page.locator('input[type="file"]').first().setInputFiles(absolutePath);
|
|
1007
|
+
log.success(" ✅ File uploaded via upload button click + setInputFiles");
|
|
1008
|
+
await randomDelay(1000, 2000);
|
|
1009
|
+
await this.waitForSourceProcessing({ mode: "lenient" });
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
catch (e) {
|
|
1014
|
+
log.info(` Playwright click approach: ${e}`);
|
|
1015
|
+
}
|
|
1016
|
+
try {
|
|
1017
|
+
const fileChooserPromise = page.waitForEvent('filechooser', { timeout: 5000 });
|
|
1018
|
+
const chooseFileLocator = page.locator('span.dropzone__file-dialog-button');
|
|
1019
|
+
if (await chooseFileLocator.count() > 0) {
|
|
1020
|
+
await chooseFileLocator.first().click();
|
|
1021
|
+
}
|
|
1022
|
+
const fileChooser = await fileChooserPromise;
|
|
1023
|
+
await fileChooser.setFiles(absolutePath);
|
|
1024
|
+
log.success(" ✅ File uploaded via filechooser event");
|
|
1025
|
+
await randomDelay(1000, 2000);
|
|
1026
|
+
await this.waitForSourceProcessing({ mode: "lenient" });
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
catch (e) {
|
|
1030
|
+
log.info(` Filechooser approach: ${e}`);
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
const fileInputLocator = page.locator('input[type="file"]');
|
|
1034
|
+
if (await fileInputLocator.count() > 0) {
|
|
1035
|
+
await fileInputLocator.first().setInputFiles(absolutePath);
|
|
1036
|
+
log.success(" ✅ File uploaded via existing locator");
|
|
1037
|
+
await randomDelay(1000, 2000);
|
|
1038
|
+
await this.waitForSourceProcessing({ mode: "lenient" });
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
catch (e) {
|
|
1043
|
+
log.info(` Existing locator attempt: ${e}`);
|
|
1044
|
+
}
|
|
1045
|
+
throw new Error("Could not upload file - all methods failed. NotebookLM may be using an unsupported upload method.");
|
|
1046
|
+
}
|
|
1047
|
+
async clickSourceTypeByText(textPatterns) {
|
|
1048
|
+
const page = this.requirePage();
|
|
1049
|
+
for (const pattern of textPatterns) {
|
|
1050
|
+
try {
|
|
1051
|
+
const clicked = await page.evaluate((searchText) => {
|
|
1052
|
+
const browser = globalThis;
|
|
1053
|
+
const elements = Array.from(browser.document.querySelectorAll('button, [role="button"], mat-chip, mat-chip-option, [mat-chip-option]'));
|
|
1054
|
+
for (const el of elements) {
|
|
1055
|
+
const text = el.textContent?.trim() || "";
|
|
1056
|
+
const aria = el.getAttribute("aria-label")?.trim() || "";
|
|
1057
|
+
const haystack = `${text} ${aria}`.toLowerCase();
|
|
1058
|
+
if (haystack.includes("search the web") || haystack.includes("discover sources")) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if ((text === searchText || haystack.includes(searchText.toLowerCase())) &&
|
|
1062
|
+
el.offsetParent !== null) {
|
|
1063
|
+
el.click();
|
|
1064
|
+
return true;
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return false;
|
|
1068
|
+
}, pattern);
|
|
1069
|
+
if (clicked) {
|
|
1070
|
+
log.success(`✅ Clicked source type: ${pattern}`);
|
|
1071
|
+
await randomDelay(800, 1200);
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
catch (err) {
|
|
1076
|
+
log.debug(`source-manager: clicking source type tab via text pattern: ${err instanceof Error ? err.message : String(err)}`);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const visibleOptions = await this.getVisibleSourceOptions(this.requirePage());
|
|
1080
|
+
log.warning(`⚠️ Could not find source type: ${textPatterns.join(", ")} — visible: ${JSON.stringify(visibleOptions)}`);
|
|
1081
|
+
}
|
|
1082
|
+
async findValidTextInputSelector(page) {
|
|
1083
|
+
for (const selector of getSelectors("textInput")) {
|
|
1084
|
+
try {
|
|
1085
|
+
const state = await page.evaluate((targetSelector) => {
|
|
1086
|
+
const browser = globalThis;
|
|
1087
|
+
const textarea = browser.document.querySelector(targetSelector);
|
|
1088
|
+
if (!textarea || textarea.offsetParent === null) {
|
|
1089
|
+
return { matches: false, rejected: "missing" };
|
|
1090
|
+
}
|
|
1091
|
+
const aria = textarea.getAttribute("aria-label")?.toLowerCase() || "";
|
|
1092
|
+
const placeholder = textarea.getAttribute("placeholder")?.toLowerCase() || "";
|
|
1093
|
+
const className = textarea.className?.toLowerCase() || "";
|
|
1094
|
+
const haystack = `${aria} ${placeholder} ${className}`;
|
|
1095
|
+
if (haystack.includes("discover sources") ||
|
|
1096
|
+
haystack.includes("search the web") ||
|
|
1097
|
+
haystack.includes("query-box")) {
|
|
1098
|
+
return { matches: false, rejected: haystack };
|
|
1099
|
+
}
|
|
1100
|
+
return { matches: true, rejected: "" };
|
|
1101
|
+
}, selector);
|
|
1102
|
+
if (state.matches) {
|
|
1103
|
+
return selector;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
catch (err) {
|
|
1107
|
+
log.debug(`source-manager: validating text source input selector: ${err instanceof Error ? err.message : String(err)}`);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
async getVisibleSourceOptions(page) {
|
|
1113
|
+
try {
|
|
1114
|
+
return await page.evaluate(() => {
|
|
1115
|
+
const browser = globalThis;
|
|
1116
|
+
return Array.from(browser.document.querySelectorAll('button, [role="button"], mat-chip, mat-chip-option, [mat-chip-option]'))
|
|
1117
|
+
.map((el) => el)
|
|
1118
|
+
.filter((el) => el.offsetParent !== null)
|
|
1119
|
+
.map((el) => `${el.textContent?.trim() || ""} ${el.getAttribute("aria-label") || ""}`.trim())
|
|
1120
|
+
.filter((text) => text.length > 0)
|
|
1121
|
+
.slice(0, 20);
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
catch (err) {
|
|
1125
|
+
log.debug(`source-manager: collecting visible source options: ${err instanceof Error ? err.message : String(err)}`);
|
|
1126
|
+
return [];
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async getVisibleTextareas(page) {
|
|
1130
|
+
try {
|
|
1131
|
+
return await page.evaluate(() => {
|
|
1132
|
+
const browser = globalThis;
|
|
1133
|
+
return Array.from(browser.document.querySelectorAll("textarea"))
|
|
1134
|
+
.map((el) => el)
|
|
1135
|
+
.filter((el) => el.offsetParent !== null)
|
|
1136
|
+
.map((el) => ({
|
|
1137
|
+
selector: `textarea.${el.className?.replace(/\s+/g, ".")}` || "textarea",
|
|
1138
|
+
aria: el.getAttribute("aria-label") || "",
|
|
1139
|
+
placeholder: el.getAttribute("placeholder") || "",
|
|
1140
|
+
classes: el.className || "",
|
|
1141
|
+
}));
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
catch (err) {
|
|
1145
|
+
log.debug(`source-manager: collecting visible textareas: ${err instanceof Error ? err.message : String(err)}`);
|
|
1146
|
+
return [];
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
async clickSubmitButton() {
|
|
1150
|
+
const page = this.requirePage();
|
|
1151
|
+
const selectors = getSelectors("submitButton");
|
|
1152
|
+
for (const selector of selectors) {
|
|
1153
|
+
try {
|
|
1154
|
+
const element = await page.$(selector);
|
|
1155
|
+
if (element && await element.isVisible()) {
|
|
1156
|
+
await element.click();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
catch (err) {
|
|
1161
|
+
log.debug(`source-manager: clicking submit button selector: ${err instanceof Error ? err.message : String(err)}`);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
await page.keyboard.press("Enter");
|
|
1165
|
+
}
|
|
1166
|
+
async clickInsertButton() {
|
|
1167
|
+
const page = this.requirePage();
|
|
1168
|
+
const clicked = await page.evaluate(() => {
|
|
1169
|
+
const browser = globalThis;
|
|
1170
|
+
const submitBtn = browser.document.querySelector("button[type='submit']:not([disabled])");
|
|
1171
|
+
if (submitBtn && submitBtn.offsetParent !== null) {
|
|
1172
|
+
submitBtn.click();
|
|
1173
|
+
return true;
|
|
1174
|
+
}
|
|
1175
|
+
const primaryBtn = browser.document.querySelector("button.button-color--primary:not([disabled])");
|
|
1176
|
+
if (primaryBtn && primaryBtn.offsetParent !== null) {
|
|
1177
|
+
primaryBtn.click();
|
|
1178
|
+
return true;
|
|
1179
|
+
}
|
|
1180
|
+
const buttons = Array.from(browser.document.querySelectorAll("button"));
|
|
1181
|
+
for (const btn of buttons) {
|
|
1182
|
+
const text = btn.textContent?.trim() || "";
|
|
1183
|
+
if (text === "Insert" || text.toLowerCase() === "insert") {
|
|
1184
|
+
btn.click();
|
|
1185
|
+
return true;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return false;
|
|
1189
|
+
});
|
|
1190
|
+
if (clicked) {
|
|
1191
|
+
log.success("✅ Clicked 'Insert' button");
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
log.warning("⚠️ 'Insert' button not found, trying submit button");
|
|
1195
|
+
await this.clickSubmitButton();
|
|
1196
|
+
}
|
|
1197
|
+
async waitForSourceProcessing(options = { mode: "strict" }) {
|
|
1198
|
+
const page = this.requirePage();
|
|
1199
|
+
log.info("⏳ Waiting for source processing...");
|
|
1200
|
+
if (options.mode === "lenient") {
|
|
1201
|
+
await randomDelay(3000, 4000);
|
|
1202
|
+
const dialogStillOpen = await this.isSourceDialogOpen();
|
|
1203
|
+
if (!dialogStillOpen) {
|
|
1204
|
+
log.success("✅ Source dialog closed - assuming success");
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
await this.throwIfSourceErrorAlert(page);
|
|
1208
|
+
await randomDelay(2000, 3000);
|
|
1209
|
+
log.success("✅ Source processing appears complete");
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const timeout = LENIENT_SOURCE_PROCESSING_TIMEOUT_MS;
|
|
1213
|
+
const startTime = Date.now();
|
|
1214
|
+
while (Date.now() - startTime < timeout) {
|
|
1215
|
+
const successElement = await findElement(page, "successIndicator");
|
|
1216
|
+
if (successElement) {
|
|
1217
|
+
log.success("✅ Source processed successfully");
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const errorElement = await findElement(page, "errorMessage");
|
|
1221
|
+
if (errorElement) {
|
|
1222
|
+
const errorText = (await errorElement.innerText?.()) ||
|
|
1223
|
+
"Unknown error";
|
|
1224
|
+
throw new Error(`Source processing failed: ${errorText}`);
|
|
1225
|
+
}
|
|
1226
|
+
const processingElement = await findElement(page, "processingIndicator");
|
|
1227
|
+
if (!processingElement) {
|
|
1228
|
+
await randomDelay(1000, 1500);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
await page.waitForTimeout(1000);
|
|
1232
|
+
}
|
|
1233
|
+
log.warning("⚠️ Source processing timeout - continuing anyway");
|
|
1234
|
+
}
|
|
1235
|
+
async throwIfSourceErrorAlert(page) {
|
|
1236
|
+
const hasError = await page.evaluate(() => {
|
|
1237
|
+
const browser = globalThis;
|
|
1238
|
+
const alerts = Array.from(browser.document.querySelectorAll('[role="alert"]'));
|
|
1239
|
+
for (const alert of alerts) {
|
|
1240
|
+
const text = alert.textContent?.toLowerCase() || "";
|
|
1241
|
+
if (text.includes("error") || text.includes("failed") || text.includes("invalid") || text.includes("unable")) {
|
|
1242
|
+
return text.substring(0, 100);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
});
|
|
1247
|
+
if (hasError) {
|
|
1248
|
+
throw new Error(`Source processing failed: ${hasError}`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
586
1252
|
//# sourceMappingURL=source-manager.js.map
|