319 lines
9.1 KiB
JavaScript
319 lines
9.1 KiB
JavaScript
/**
|
|
* Example tests demonstrating Playwright with human-like behavior
|
|
* Run with: npx playwright test tests/human-behavior.test.js --headed
|
|
*/
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
import {
|
|
randomDelay,
|
|
humanMouseMove,
|
|
randomMouseMovements,
|
|
humanScroll,
|
|
humanClick,
|
|
humanType,
|
|
simulateReading,
|
|
getHumanizedContext
|
|
} from '../scripts/human-behavior.js';
|
|
|
|
test.describe('Human-like behavior tests', () => {
|
|
|
|
test('should navigate and search Google with human behavior', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
// Navigate to Google
|
|
await page.goto('https://www.google.com');
|
|
await randomDelay(1000, 2000);
|
|
|
|
// Random mouse movements
|
|
await randomMouseMovements(page, 2);
|
|
|
|
// Find search box
|
|
const searchBox = 'textarea[name="q"], input[name="q"]';
|
|
await page.waitForSelector(searchBox);
|
|
|
|
// Click and type with human behavior
|
|
await humanClick(page, searchBox);
|
|
await humanType(page, searchBox, 'playwright testing', {
|
|
minDelay: 80,
|
|
maxDelay: 200,
|
|
mistakes: 0.05
|
|
});
|
|
|
|
// Submit search
|
|
await randomDelay(500, 1000);
|
|
await page.keyboard.press('Enter');
|
|
|
|
// Wait for results
|
|
await page.waitForLoadState('networkidle');
|
|
await randomDelay(1500, 2500);
|
|
|
|
// Scroll through results
|
|
await humanScroll(page, {
|
|
scrollCount: 3,
|
|
minScroll: 150,
|
|
maxScroll: 400,
|
|
randomDirection: true
|
|
});
|
|
|
|
// Simulate reading
|
|
await simulateReading(page, 3000);
|
|
|
|
// Verify we have results
|
|
const results = await page.locator('div.g').count();
|
|
expect(results).toBeGreaterThan(0);
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
test('should scroll with natural human patterns', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
// Navigate to a long page
|
|
await page.goto('https://en.wikipedia.org/wiki/Web_scraping');
|
|
await randomDelay(1000, 2000);
|
|
|
|
// Get initial scroll position
|
|
const initialScroll = await page.evaluate(() => window.scrollY);
|
|
|
|
// Perform human-like scrolling
|
|
await humanScroll(page, {
|
|
scrollCount: 5,
|
|
minScroll: 100,
|
|
maxScroll: 300,
|
|
minDelay: 800,
|
|
maxDelay: 2000,
|
|
randomDirection: true
|
|
});
|
|
|
|
// Verify page scrolled
|
|
const finalScroll = await page.evaluate(() => window.scrollY);
|
|
expect(finalScroll).not.toBe(initialScroll);
|
|
|
|
// Add some random mouse movements
|
|
await randomMouseMovements(page, 3);
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
test('should click elements with overshooting', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
await page.goto('https://www.example.com');
|
|
await randomDelay(1000, 1500);
|
|
|
|
// Move mouse around naturally
|
|
await randomMouseMovements(page, 2);
|
|
|
|
// Click with human behavior (with possible overshoot)
|
|
const linkSelector = 'a';
|
|
await humanClick(page, linkSelector, {
|
|
overshootChance: 0.3, // 30% chance to overshoot
|
|
overshootDistance: 25
|
|
});
|
|
|
|
// Wait for navigation
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Verify navigation occurred
|
|
const url = page.url();
|
|
expect(url).toContain('iana.org');
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
test('should simulate realistic reading behavior', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
await page.goto('https://news.ycombinator.com');
|
|
await randomDelay(1000, 2000);
|
|
|
|
const startTime = Date.now();
|
|
|
|
// Simulate reading for 5 seconds
|
|
await simulateReading(page, 5000);
|
|
|
|
const elapsed = Date.now() - startTime;
|
|
|
|
// Should have taken at least 5 seconds
|
|
expect(elapsed).toBeGreaterThanOrEqual(5000);
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
test('should use randomized browser fingerprints', async ({ browser }) => {
|
|
// Create multiple contexts and verify they have different fingerprints
|
|
const contexts = [];
|
|
|
|
try {
|
|
for (let i = 0; i < 3; i++) {
|
|
const context = await getHumanizedContext(browser);
|
|
contexts.push(context);
|
|
}
|
|
|
|
// Each context should have different settings
|
|
expect(contexts.length).toBe(3);
|
|
|
|
// Verify different user agents (likely, due to randomization)
|
|
const page1 = await contexts[0].newPage();
|
|
const page2 = await contexts[1].newPage();
|
|
|
|
const ua1 = await page1.evaluate(() => navigator.userAgent);
|
|
const ua2 = await page2.evaluate(() => navigator.userAgent);
|
|
|
|
// Both should be valid user agents
|
|
expect(ua1).toBeTruthy();
|
|
expect(ua2).toBeTruthy();
|
|
|
|
await page1.close();
|
|
await page2.close();
|
|
|
|
} finally {
|
|
for (const context of contexts) {
|
|
await context.close();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should type with realistic mistakes and corrections', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
await page.goto('https://www.google.com');
|
|
await randomDelay(1000, 1500);
|
|
|
|
const searchBox = 'textarea[name="q"], input[name="q"]';
|
|
await page.waitForSelector(searchBox);
|
|
|
|
// Type with high mistake chance for testing
|
|
await humanClick(page, searchBox);
|
|
await humanType(page, searchBox, 'testing human behavior', {
|
|
minDelay: 50,
|
|
maxDelay: 120,
|
|
mistakes: 0.1 // 10% mistake rate for testing
|
|
});
|
|
|
|
// Get the input value
|
|
const value = await page.inputValue(searchBox);
|
|
|
|
// Should contain the text (might have slight variations due to mistakes)
|
|
expect(value.toLowerCase()).toContain('testing');
|
|
expect(value.toLowerCase()).toContain('behavior');
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
test.describe('Google Alert validation examples', () => {
|
|
|
|
test('should validate a simple Google Alert query', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
// Test query for MacBook repair in Toronto
|
|
const query = '"macbook repair" Toronto';
|
|
|
|
await page.goto('https://www.google.com');
|
|
await randomDelay(1000, 2000);
|
|
|
|
// Perform search
|
|
const searchBox = 'textarea[name="q"], input[name="q"]';
|
|
await humanClick(page, searchBox);
|
|
await humanType(page, searchBox, query);
|
|
await randomDelay(500, 1000);
|
|
await page.keyboard.press('Enter');
|
|
|
|
// Wait for results
|
|
await page.waitForLoadState('networkidle');
|
|
await randomDelay(1500, 2500);
|
|
|
|
// Check if we got results
|
|
const resultCount = await page.locator('div.g').count();
|
|
expect(resultCount).toBeGreaterThan(0);
|
|
|
|
// Scroll through results naturally
|
|
await humanScroll(page, { scrollCount: 2 });
|
|
|
|
// Simulate reading
|
|
await simulateReading(page, 2000);
|
|
|
|
console.log(`✅ Query "${query}" returned ${resultCount} results`);
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
test('should validate Reddit-specific query', async ({ browser }) => {
|
|
const context = await getHumanizedContext(browser);
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
// Reddit-specific query
|
|
const query = 'site:reddit.com/r/toronto "laptop repair"';
|
|
|
|
await page.goto('https://www.google.com');
|
|
await randomDelay(1000, 2000);
|
|
|
|
// Perform search with human behavior
|
|
const searchBox = 'textarea[name="q"], input[name="q"]';
|
|
await humanClick(page, searchBox);
|
|
await humanType(page, searchBox, query, { mistakes: 0.03 });
|
|
await randomDelay(500, 1200);
|
|
await page.keyboard.press('Enter');
|
|
|
|
// Wait and analyze
|
|
await page.waitForLoadState('networkidle');
|
|
await randomDelay(2000, 3000);
|
|
|
|
// Natural scrolling
|
|
await humanScroll(page, {
|
|
scrollCount: 2,
|
|
minScroll: 200,
|
|
maxScroll: 500
|
|
});
|
|
|
|
// Extract results
|
|
const results = await page.evaluate(() => {
|
|
const items = document.querySelectorAll('div.g');
|
|
return Array.from(items).length;
|
|
});
|
|
|
|
console.log(`✅ Reddit query returned ${results} results`);
|
|
expect(results).toBeGreaterThanOrEqual(0);
|
|
|
|
} finally {
|
|
await page.close();
|
|
await context.close();
|
|
}
|
|
});
|
|
|
|
});
|
|
|