blob: bb6d118f83df5844097d0969c801ca595fc62e96 [file] [edit]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {execSync} from 'node:child_process';
import type * as puppeteer from 'puppeteer-core';
export async function chromeLogin(page: puppeteer.Page, username: string): Promise<void> {
await page.goto('https://accounts.google.com', {waitUntil: 'networkidle2'});
if (page.url().includes('myaccount.google.com')) {
return;
}
await fillGoogleLoginPage(page, username);
await page.waitForFunction(() => window.location.href.includes('myaccount.google.com'));
}
export async function fillGoogleLoginPage(page: puppeteer.Page, username: string): Promise<void> {
const password = getOtaPassword(username);
const nextBtn = await page.waitForSelector('#identifierNext, #passwordNext', {visible: true});
if (!nextBtn) {
throw new Error('Could not find Next button during login');
}
const isIdentifier = await nextBtn.evaluate((el: Element) => el.id === 'identifierNext');
if (isIdentifier) {
await typeTextIn(page, 'input[name="identifier"]', username);
await page.click('#identifierNext');
}
const renderedInput = await page.waitForSelector('input[type="password"], input[name="contact"]', {visible: true});
const isContactChallenge =
await renderedInput?.evaluate((el: Element) => (el as HTMLInputElement).name === 'contact');
if (isContactChallenge) {
throw new Error('Account verification from trusted contact required!');
}
await typeTextIn(page, 'input[type="password"]', password);
await page.click('#passwordNext');
}
export async function typeTextIn(page: puppeteer.Page, selector: string, value: string) {
await page.focus(selector);
await page.keyboard.type(value);
}
export async function configureDevToolsPreferences(page: puppeteer.Page) {
const devtoolsPage = await page.openDevTools();
if (!devtoolsPage) {
throw new Error('Could not open DevTools page!');
}
await devtoolsPage.waitForFunction(() => window.location.origin.startsWith('devtools://'), {timeout: 1000})
.catch(() => {});
await devtoolsPage.evaluate(() => {
localStorage.setItem('debugAiAssistancePanelEnabled', 'true');
localStorage.setItem('aiAssistanceStructuredLogEnabled', 'true');
});
await devtoolsPage.close().catch((e: unknown) => console.error('Failed to close DevTools page:', e));
}
export function getOtaPassword(email: string): string {
const cmd =
`stubby call blade:identity-testaccount-service-prod TestaccountService.ChangeTestAccountPassword 'email: "${
email}" generate_string_password: true' --output_json`;
try {
const output = execSync(cmd, {encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe']});
const parsed = JSON.parse(output);
if (!parsed || typeof parsed !== 'object' || !parsed.string_password) {
throw new Error(`Could not find string_password in stubby JSON output:\n${output}`);
}
return parsed.string_password;
} catch (error) {
console.error(`Failed to get password for ${email}.`);
if (error && typeof error === 'object' && 'stderr' in error) {
console.error(`stubby error:\n${error.stderr}`);
}
throw error;
}
}