/** * NotebookLM UI Selectors * * These selectors are used for notebook creation automation. * Run the selector discovery tool to update these with actual values. * * Usage: * node dist/notebook-creation/run-discovery.js * * Note: Must be authenticated first via setup_auth tool. */ /** * CSS selectors to find assistant response elements. * Keep NotebookLM selector knowledge in this module so UI drift is audited in * one place. */ export declare const RESPONSE_SELECTORS: readonly [".to-user-container .message-text-content", "[data-message-author='bot']", "[data-message-author='assistant']", "[data-message-role='assistant']", "[data-author='assistant']", "[data-renderer*='assistant']", "[data-automation-id='response-text']", "[data-automation-id='assistant-response']", "[data-automation-id='chat-response']", "[data-testid*='assistant']", "[data-testid*='response']", "[aria-live='polite']", "[role='listitem'][data-message-author]"]; export declare const NOTEBOOKLM_SELECTORS: { /** New notebook / Create button on homepage * Discovered: "addCreate new" with aria="Create new notebook" */ readonly newNotebookButton: { readonly primary: "button[aria-label=\"Create new notebook\"]"; readonly fallbacks: readonly ["button[aria-label*=\"Create new\"]", "button[aria-label*=\"Create\"]"]; readonly confirmed: true; }; /** Notebook name input field * Note: NotebookLM auto-creates notebook with default name. * Name can be edited later via the title element. * Primary is scoped to an Angular Material dialog to avoid matching * random text inputs elsewhere on the page. */ readonly notebookNameInput: { readonly primary: "header [contenteditable=\"true\"]"; readonly fallbacks: readonly ["h1[contenteditable=\"true\"]", "[role=\"heading\"][contenteditable=\"true\"]", "[contenteditable=\"true\"][aria-label*=\"title\" i]", "[contenteditable=\"true\"][aria-label*=\"name\" i]", "input[aria-label*=\"name\" i]", "input[aria-label*=\"title\" i]"]; readonly confirmed: false; }; /** Add source / Upload source button * Discovered: aria="Add source" with class "add-source-button" * Locale note: class selector is locale-independent; aria-label is English-only */ readonly addSourceButton: { readonly primary: "button.add-source-button"; readonly fallbacks: readonly ["button[aria-label=\"Add source\"]", "button[aria-label*=\"Add source\"]", "button[aria-label=\"Opens the upload source dialogue\"]"]; readonly confirmed: true; }; /** URL/Discover sources option (for adding URLs) * Discovered: "search_sparkDiscover sources" button * Locale note: aria-label text is locale-dependent */ readonly urlSourceOption: { readonly primary: "button[class*=\"url-source\"], button[class*=\"discover\"]"; readonly fallbacks: readonly ["button[aria-label*=\"Discover\"]", "button[aria-label*=\"URL\" i]", "mat-chip[value=\"url\"]"]; readonly confirmed: true; }; /** Text/Paste source option * Locale note: aria-label text is locale-dependent */ readonly textSourceOption: { readonly primary: "mat-chip[value=\"text\"], button[value=\"text\"], [data-source-type=\"text\"], [data-type=\"text\"]"; readonly fallbacks: readonly ["mat-chip-option[value=\"text\"]", "[mat-chip-option][value=\"text\"]", "button[aria-label*=\"Copied text\"]", "button[aria-label*=\"Paste\"]", "button[class*=\"text-source\"]"]; readonly confirmed: false; }; /** File upload source option * Discovered: aria="Upload sources from your computer" * Locale note: aria-label text is locale-dependent */ readonly fileSourceOption: { readonly primary: "button[class*=\"upload\"], input[type=\"file\"] + button"; readonly fallbacks: readonly ["button[aria-label=\"Upload sources from your computer\"]", "button[aria-label*=\"Upload\"]", "span[role=\"button\"]"]; readonly confirmed: true; }; /** URL input field - appears after clicking Discover sources */ readonly urlInput: { readonly primary: "input[type=\"url\"]"; readonly fallbacks: readonly ["input[type=\"text\"][placeholder*=\"URL\"]", "input[type=\"text\"][placeholder*=\"http\"]", "input[aria-label*=\"URL\"]", "textarea[placeholder*=\"URL\"]"]; readonly confirmed: false; }; /** Text input/paste area - appears after clicking "Copied text" * Discovered: class contains "text-area" */ readonly textInput: { readonly primary: "textarea.text-area:not(.query-box-input):not([aria-label*=\"discover sources\" i]):not([placeholder*=\"search the web\" i])"; readonly fallbacks: readonly ["textarea[class*=\"text-area\"]:not(.query-box-input):not([aria-label*=\"discover sources\" i]):not([placeholder*=\"search the web\" i])", "textarea.mat-mdc-form-field-textarea-control:not(.query-box-input):not([aria-label*=\"discover sources\" i]):not([placeholder*=\"search the web\" i])", "mat-dialog-container textarea:not([readonly]):not(.query-box-input):not([aria-label*=\"discover sources\" i]):not([placeholder*=\"search the web\" i])"]; readonly confirmed: true; }; /** File input element * Discovered: "choose file" span triggers hidden input[type="file"] * Updated: December 2025 - dropzone UI */ readonly fileInput: { readonly primary: "input[type=\"file\"]"; readonly fallbacks: readonly []; readonly confirmed: true; }; /** Choose file button in dropzone (December 2025 UI) */ readonly chooseFileButton: { readonly primary: "span.dropzone__file-dialog-button"; readonly fallbacks: readonly ["span[xapscottyuploadertrigger]", "[class*=\"file-dialog-button\"]", "button[class*=\"upload\"][class*=\"trigger\"]", "span[class*=\"file-dialog\"]", "a[aria-label*=\"choose file\" i]", "a[href][class*=\"file\"]"]; readonly confirmed: true; }; /** Submit/Add button — scoped to Angular Material dialog actions so we * don't match arbitrary submit buttons elsewhere on the page. * Discovered: "Insert" button for text sources, "Submit" for chat. * Text-engine (:has-text) kept as fallback for locale-agnostic routes. */ readonly submitButton: { readonly primary: "mat-dialog-actions button[type=\"submit\"]"; readonly fallbacks: readonly ["[role=\"dialog\"] button[type=\"submit\"]", "mat-dialog-actions button[color=\"primary\"]", "button[type=\"submit\"]", "button:has-text(\"Insert\")", "button[aria-label=\"Submit\"]", "button[aria-label*=\"Add\"]"]; readonly confirmed: true; }; /** Close dialog button * Discovered: aria="Close dialogue" (British spelling) * Note: Google uses British "dialogue" — US "dialog" added as fallback */ readonly closeDialogButton: { readonly primary: "button[aria-label=\"Close dialogue\"]"; readonly fallbacks: readonly ["button[aria-label=\"Close dialog\"]", "button[aria-label=\"Close\"]", "button[aria-label*=\"close\" i]"]; readonly confirmed: true; }; /** Processing/Loading indicator */ readonly processingIndicator: { readonly primary: "[role=\"progressbar\"]"; readonly fallbacks: readonly ["[aria-label*=\"loading\" i]", "[aria-label*=\"processing\" i]", ".loading", ".spinner"]; readonly confirmed: false; }; /** Success indicator */ readonly successIndicator: { readonly primary: "[aria-label*=\"success\" i]"; readonly fallbacks: readonly ["[data-status=\"complete\"]", ".source-added"]; readonly confirmed: false; }; /** Error message element */ readonly errorMessage: { readonly primary: "[role=\"alert\"]"; readonly fallbacks: readonly ["[aria-live=\"polite\"]", ".error-message"]; readonly confirmed: false; }; /** Chat input (existing - for reference) */ readonly chatInput: { readonly primary: "textarea.query-box-input"; readonly fallbacks: readonly ["textarea[aria-label][class*=\"query\"]", ".chat-input textarea[aria-label]", "textarea[class*=\"query\"]", ".chat-input textarea", "textarea[aria-label=\"Query box\"]"]; readonly confirmed: true; }; }; export type SelectorKey = keyof typeof NOTEBOOKLM_SELECTORS; /** * Get all selectors for a key (primary + fallbacks) */ export declare function getSelectors(key: SelectorKey): string[];