@knowcode/screenshotfetch
Version:
Web application spider with screenshot capture and customer journey documentation. Automate user flow documentation with authentication support.
256 lines (223 loc) • 7.6 kB
JavaScript
class LoginHandler {
constructor(options = {}) {
this.options = {
timeout: 10000,
waitTime: 2000,
...options
};
// Common login form selectors
this.usernameSelectors = [
'input[name="username"]',
'input[name="email"]',
'input[name="user"]',
'input[name="login"]',
'input[type="email"]',
'input[id*="username" i]',
'input[id*="email" i]',
'input[id*="user" i]',
'input[id*="login" i]',
'input[placeholder*="username" i]',
'input[placeholder*="email" i]',
'input[placeholder*="user" i]',
'input[class*="username" i]',
'input[class*="email" i]',
'input[class*="user" i]'
];
this.passwordSelectors = [
'input[name="password"]',
'input[name="pass"]',
'input[type="password"]',
'input[id*="password" i]',
'input[id*="pass" i]',
'input[placeholder*="password" i]',
'input[class*="password" i]',
'input[class*="pass" i]'
];
this.submitSelectors = [
'button[type="submit"]',
'input[type="submit"]',
'button[id*="login" i]',
'button[id*="signin" i]',
'button[class*="login" i]',
'button[class*="signin" i]',
'button[class*="submit" i]',
'[data-testid*="login" i]',
'[data-testid*="signin" i]',
'[data-testid*="submit" i]'
];
}
async detectLoginForm(page) {
console.log('🔍 Detecting login form...');
try {
// Look for username/email field
const usernameField = await this.findElement(page, this.usernameSelectors);
if (!usernameField) {
console.log('❌ No username/email field found');
return null;
}
// Look for password field
const passwordField = await this.findElement(page, this.passwordSelectors);
if (!passwordField) {
console.log('❌ No password field found');
return null;
}
// Look for submit button
const submitButton = await this.findElement(page, this.submitSelectors);
if (!submitButton) {
console.log('❌ No submit button found');
return null;
}
console.log('✅ Login form detected');
return {
usernameField,
passwordField,
submitButton
};
} catch (error) {
console.error('❌ Error detecting login form:', error.message);
return null;
}
}
async findElement(page, selectors) {
for (const selector of selectors) {
try {
await page.waitForSelector(selector, { timeout: 1000, visible: true });
return selector;
} catch (e) {
// Continue to next selector
}
}
return null;
}
async performLogin(page, username, password) {
console.log('🔐 Attempting to log in...');
// Detect login form
const form = await this.detectLoginForm(page);
if (!form) {
throw new Error('Login form not found on page');
}
try {
// Clear and fill username field
await page.click(form.usernameField);
await page.keyboard.down('Control');
await page.keyboard.press('KeyA');
await page.keyboard.up('Control');
await page.type(form.usernameField, username);
console.log('✅ Username entered');
// Clear and fill password field
await page.click(form.passwordField);
await page.keyboard.down('Control');
await page.keyboard.press('KeyA');
await page.keyboard.up('Control');
await page.type(form.passwordField, password);
console.log('✅ Password entered');
// Submit the form
await page.click(form.submitButton);
console.log('✅ Login form submitted');
// Wait for navigation or page change
await new Promise(resolve => setTimeout(resolve, this.options.waitTime));
// Check if login was successful
const isLoggedIn = await this.verifyLogin(page);
if (isLoggedIn) {
console.log('✅ Login successful');
return true;
} else {
console.log('❌ Login may have failed - no clear success indicators');
return false;
}
} catch (error) {
console.error('❌ Login failed:', error.message);
throw new Error(`Login failed: ${error.message}`);
}
}
async verifyLogin(page) {
try {
// Common indicators of successful login
const successIndicators = [
// Look for logout button/link
'a[href*="logout" i]',
'button[onclick*="logout" i]',
'[data-testid*="logout" i]',
// Look for user profile elements
'.user-profile',
'.user-menu',
'[class*="user-avatar"]',
'[class*="profile"]',
// Look for dashboard elements
'.dashboard',
'[class*="dashboard"]',
// Look for navigation that suggests logged in state
'[class*="main-nav"]',
'[class*="app-nav"]'
];
for (const selector of successIndicators) {
try {
await page.waitForSelector(selector, { timeout: 2000, visible: true });
console.log(`✅ Login success indicator found: ${selector}`);
return true;
} catch (e) {
// Continue checking other indicators
}
}
// Check if we're still on a login page (negative indicator)
const currentUrl = page.url();
const isStillOnLogin = currentUrl.includes('login') ||
currentUrl.includes('signin') ||
currentUrl.includes('auth');
if (!isStillOnLogin) {
console.log('✅ URL changed from login page, assuming success');
return true;
}
// Look for error messages (negative indicator)
const errorSelectors = [
'.error',
'.alert-error',
'[class*="error"]',
'[class*="invalid"]',
'[data-testid*="error"]'
];
for (const selector of errorSelectors) {
try {
await page.waitForSelector(selector, { timeout: 1000, visible: true });
console.log(`❌ Login error indicator found: ${selector}`);
return false;
} catch (e) {
// Continue checking
}
}
// If no clear indicators either way, assume success if URL changed
return !isStillOnLogin;
} catch (error) {
console.log('⚠️ Could not verify login status:', error.message);
return false;
}
}
async isLoginRequired(page) {
// Check if current page looks like a login page
const currentUrl = page.url().toLowerCase();
const isLoginUrl = currentUrl.includes('login') ||
currentUrl.includes('signin') ||
currentUrl.includes('auth') ||
currentUrl.includes('sign-in');
if (isLoginUrl) {
return true;
}
// Check for login form on current page
const form = await this.detectLoginForm(page);
return form !== null;
}
async waitForLoginPage(page, timeout = 10000) {
console.log('⏳ Waiting for login page to load...');
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
if (await this.isLoginRequired(page)) {
console.log('✅ Login page detected');
return true;
}
await new Promise(resolve => setTimeout(resolve, 500));
}
console.log('❌ Login page not detected within timeout');
return false;
}
}
module.exports = LoginHandler;