Playwright start

This commit is contained in:
2026-05-30 10:04:12 -04:00
parent 73f29cea08
commit 1f7a0819fc
108 changed files with 37445 additions and 62 deletions
+3
View File
@@ -0,0 +1,3 @@
node_modules/
test-results/
playwright-report/
+38
View File
@@ -0,0 +1,38 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('https://calm-mud-04916b210.1.azurestaticapps.net/build-calculator', { timeout: 15000 });
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.locator('select').nth(0).selectOption("Q'Rath");
await page.waitForTimeout(300);
await page.locator('select').nth(1).selectOption('Orzum');
await page.waitForTimeout(1000);
const keys = await page.locator('.keyContainer > div > div').all();
for (const key of keys) {
const text = await key.textContent();
if (text && text.trim().startsWith('TAB')) {
console.log('TAB key textContent:', text.substring(0, 500));
const innerHtml = await key.evaluate(el => el.innerHTML);
console.log('TAB key innerHTML (first 2000):', innerHtml.substring(0, 2000));
const childDivs = await key.locator('> div').all();
console.log('Entity div count:', childDivs.length);
for (const div of childDivs) {
const t = await div.textContent();
const s = await div.getAttribute('style');
console.log(' Entity:', (t || '').trim(), 'Style:', (s || 'none').substring(0, 100));
}
}
}
await page.locator('.keyContainer > div > div').filter({ hasText: 'TAB' }).first().click();
await page.waitForTimeout(1000);
const entityViewName = await page.locator('.entityClickView #entityName').textContent();
console.log('Entity view shows:', entityViewName);
const entityViewHtml = await page.locator('.entityClickView').evaluate(el => el.innerHTML.substring(0, 1000));
console.log('Entity view HTML:', entityViewHtml);
await browser.close();
})().catch(e => { console.error(e.message); process.exit(1); });
+51
View File
@@ -0,0 +1,51 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
page.on('console', msg => {
if (msg.type() === 'error' || msg.type() === 'warning')
console.log(msg.type().toUpperCase() + ':', msg.text().substring(0, 300));
});
page.on('pageerror', err => console.log('PAGE_ERR:', err.message));
await page.goto('http://localhost:5111/build-calculator', { timeout: 30000, waitUntil: 'load' });
console.log('Page load event fired');
// Read button text immediately
let buttons = page.locator('.keyContainer > div > div');
let count = await buttons.count();
console.log('Button count:', count);
let qBtn = buttons.filter({ hasText: /^Q/ }).first();
console.log('Immediate Q button text:', JSON.stringify(await qBtn.textContent()));
// Wait for Blazor to finish initializing - look for .blazor-error-boundary or just wait
// Actually, let's poll for the button text changing from what it is now
for (let i = 0; i < 15; i++) {
await page.waitForTimeout(1000);
const text = (await buttons.nth(0).textContent() || '').trim();
// Check all 19 buttons for Q and F keys
const allTexts = [];
for (let j = 0; j < count; j++) {
const t = (await buttons.nth(j).textContent() || '').trim();
if (t.toUpperCase().startsWith('Q') || t.toUpperCase().startsWith('F') || t.toUpperCase().startsWith('W') || t.toUpperCase().startsWith('E')) {
allTexts.push(` B${j}: "${t.substring(0,30)}"`);
}
}
console.log(`After ${i+1}s:`);
allTexts.forEach(t => console.log(t));
}
// Now check what the filter is currently showing
const selects = page.locator('select');
console.log('\nSelect values:');
for (let s = 0; s < await selects.count(); s++) {
const val = await selects.nth(s).inputValue();
const opts = await selects.nth(s).locator('option').allTextContents();
const selectedIdx = await selects.nth(s).evaluate(el => el.selectedIndex);
console.log(` Select ${s}: value=${val}, selectedIndex=${selectedIdx}, options=${JSON.stringify(opts.map(o=>o.trim()))}`);
}
await browser.close();
})();
+74
View File
@@ -0,0 +1,74 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
page.on('pageerror', err => console.log('PAGE_ERR:', err.message));
page.on('console', msg => {
if (msg.type() === 'error') console.log('CONSOLE_ERR:', msg.text().substring(0,200));
});
try {
await page.goto('http://localhost:5111/build-calculator', { timeout: 30000, waitUntil: 'load' });
console.log('Page loaded');
} catch (e) {
console.log('Page load error:', e.message);
}
await page.waitForTimeout(10000);
console.log('Waited 10s');
const selCount = await page.locator('select').count();
console.log('Select elements count:', selCount);
if (selCount > 0) {
for (let s = 0; s < selCount; s++) {
const opts = await page.locator('select').nth(s).locator('option').allTextContents();
console.log('Select', s, 'options:', JSON.stringify(opts.map(o => o.trim())));
}
await page.locator('select').nth(0).selectOption("Q'Rath");
console.log('Selected Q\' Rath');
await page.waitForTimeout(500);
await page.locator('select').nth(1).selectOption('Orzum');
console.log('Selected Orzum');
await page.waitForTimeout(2000);
// Log all key buttons
const buttons = page.locator('.keyContainer > div > div');
const count = await buttons.count();
console.log('=== All key buttons (' + count + ') ===');
for (let i = 0; i < count; i++) {
const txt = (await buttons.nth(i).textContent() || '').trim();
console.log('Button', i, ':', JSON.stringify(txt.substring(0,60)));
}
// Find Q button
for (let i = 0; i < count; i++) {
const txt = (await buttons.nth(i).textContent() || '');
if (txt.trim().toUpperCase().startsWith('Q')) {
console.log('\nFound Q button at index', i, ':', JSON.stringify(txt.trim().substring(0,60)));
console.log('Clicking it...');
await buttons.nth(i).click({ force: true });
break;
}
}
await page.waitForTimeout(1000);
// Check entity view
const evCount = await page.locator('.entityClickView #entityName').count();
console.log('entityName count:', evCount);
if (evCount > 0) {
const name = (await page.locator('.entityClickView #entityName').textContent() || '').trim();
console.log('entityName text:', JSON.stringify(name));
}
} else {
console.log('No select elements found!');
console.log('URL:', page.url());
const body = await page.evaluate(() => document.body.innerText.substring(0, 500));
console.log('Body text:', JSON.stringify(body));
}
await browser.close();
})();
+59
View File
@@ -0,0 +1,59 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
const page = await context.newPage();
console.log('Navigating...');
await page.goto('https://calm-mud-04916b210.1.azurestaticapps.net/build-calculator', { timeout: 30000, waitUntil: 'domcontentloaded' });
console.log('Page loaded, waiting for Blazor...');
await page.waitForTimeout(8000);
console.log('Selecting Q\'Rath...');
await page.locator('select').nth(0).selectOption("Q'Rath");
await page.waitForTimeout(500);
await page.locator('select').nth(1).selectOption('Orzum');
await page.waitForTimeout(3000);
console.log('Looking for TAB key...');
const allKeys = await page.locator('.keyContainer > div > div').all();
console.log('Total key divs found:', allKeys.length);
for (let i = 0; i < allKeys.length; i++) {
const text = await allKeys[i].textContent();
const preview = (text || '').trim().substring(0, 100);
console.log('Key ' + i + ' starts with:', preview.replace(/\n/g, ' '));
if (text && text.trim().startsWith('TAB')) {
console.log('FOUND TAB KEY at index ' + i);
const entityDivs = await allKeys[i].locator('> div').all();
console.log('Entity divs:', entityDivs.length);
for (const d of entityDivs) {
const t = await d.textContent();
console.log(' Entity:', (t || '').trim());
}
}
}
console.log('Clicking TAB key...');
try {
const tabKey = page.locator('.keyContainer > div > div').filter({ hasText: 'TAB' }).first();
await tabKey.click({ timeout: 5000 });
console.log('Click succeeded');
} catch (e) {
console.log('Click failed:', e.message.substring(0, 200));
}
await page.waitForTimeout(3000);
const entityViewCount = await page.locator('.entityClickView').count();
console.log('entityClickView count:', entityViewCount);
if (entityViewCount > 0) {
const ev = await page.locator('.entityClickView #entityName').textContent();
console.log('Entity view name:', ev);
const id = await page.locator('.entityClickView .entitiesContainer').getAttribute('id');
console.log('Entity container id:', id);
} else {
console.log('Entity click view NOT found - checking for errors...');
const errorEls = await page.locator('[class*=\"error\"], [class*=\"Error\"]').count();
console.log('Error elements:', errorEls);
}
await browser.close();
})().catch(e => { console.error(e.message); process.exit(1); });
+69
View File
@@ -0,0 +1,69 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:5111/build-calculator', { timeout: 30000, waitUntil: 'networkidle' });
await page.waitForTimeout(10000);
const selects = page.locator('select');
await selects.nth(0).selectOption("Q'Rath");
await page.waitForTimeout(500);
await selects.nth(1).selectOption('Orzum');
await page.waitForTimeout(2000);
// Check entity view before adding
let evCount = await page.locator('.entityClickView #entityName').count();
console.log('entityName count before add:', evCount);
if (evCount > 0) console.log('entityName text:', (await page.locator('.entityClickView #entityName').textContent() || '').trim());
// Check timeline intervals before
let intervals = page.locator('[class*="interval"], .timelineInterval');
console.log('interval count before add:', await intervals.count());
if ((await intervals.count()) > 0) {
console.log('first interval text:', ((await intervals.first().textContent()) || '').trim().substring(0,100));
}
// Click Q
const buttons = page.locator('.keyContainer > div > div');
const count = await buttons.count();
for (let i = 0; i < count; i++) {
const txt = (await buttons.nth(i).textContent()) || '';
if (txt.trim().toUpperCase().startsWith('Q')) {
await buttons.nth(i).click({ force: true });
console.log('Clicked Q button at index', i);
break;
}
}
await page.waitForTimeout(2000);
// Check entity view after adding
evCount = await page.locator('.entityClickView #entityName').count();
console.log('entityName count after add:', evCount);
if (evCount > 0) console.log('entityName text:', ((await page.locator('.entityClickView #entityName').textContent()) || '').trim());
// Check timeline intervals after
intervals = page.locator('[class*="interval"], .timelineInterval');
console.log('interval count after add:', await intervals.count());
if ((await intervals.count()) > 0) {
const ic = await intervals.count();
for (let i = 0; i < Math.min(ic, 3); i++) {
console.log('interval', i, 'text:', ((await intervals.nth(i).textContent()) || '').trim().substring(0,120));
}
}
// Click Clear Build Order
await page.locator('button').filter({ hasText: 'Clear Build Order' }).click();
await page.waitForTimeout(2000);
// Check entity view after clear
evCount = await page.locator('.entityClickView #entityName').count();
console.log('entityName count after clear:', evCount);
if (evCount > 0) console.log('entityName text:', ((await page.locator('.entityClickView #entityName').textContent()) || '').trim());
// Check timeline intervals after clear
intervals = page.locator('[class*="interval"], .timelineInterval');
console.log('interval count after clear:', await intervals.count());
await browser.close();
})();
+39
View File
@@ -0,0 +1,39 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:5111/build-calculator', { timeout: 30000, waitUntil: 'networkidle' });
await page.waitForTimeout(10000);
const selects = page.locator('select');
await selects.nth(0).selectOption("Q'Rath");
await page.waitForTimeout(500);
await selects.nth(1).selectOption('Orzum');
await page.waitForTimeout(2000);
const grid = page.locator('.calculatorGrid > div');
const gCount = await grid.count();
console.log('calculatorGrid child divs:', gCount);
for (let i = 0; i < gCount; i++) {
const cls = await grid.nth(i).getAttribute('class');
const text = (await grid.nth(i).textContent() || '').trim().substring(0,80);
console.log(' child', i, 'class:', JSON.stringify(cls), 'text:', JSON.stringify(text));
}
// Check for interval-related elements
for (const sel of ['[class*="interval"]', '[class*="Interval"]', '[class*="timeline"]', '[class*="Timeline"]']) {
console.log(sel, 'count:', await page.locator(sel).count());
}
// Also check displayContainer children
const dc = page.locator('.displayContainer');
const dcCount = await dc.count();
console.log('displayContainer count:', dcCount);
for (let i = 0; i < dcCount; i++) {
const cls = await dc.nth(i).getAttribute('class');
const text = (await dc.nth(i).textContent() || '').trim().substring(0,150);
console.log(' dc', i, 'class:', JSON.stringify(cls), 'text:', JSON.stringify(text));
}
await browser.close();
})();
+90
View File
@@ -0,0 +1,90 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:5111/build-calculator', { timeout: 30000, waitUntil: 'networkidle' });
await page.waitForTimeout(10000);
const selects = page.locator('select');
await selects.nth(0).selectOption("Q'Rath");
await page.waitForTimeout(500);
await selects.nth(1).selectOption('Orzum');
await page.waitForTimeout(2000);
const gridItems = page.locator('.calculatorGrid > div');
const gCount = await gridItems.count();
// Find the timeline section (has "Shows economy" text)
let timelineIdx = -1;
for (let i = 0; i < gCount; i++) {
const txt = (await gridItems.nth(i).textContent() || '');
if (txt.includes('economy')) {
timelineIdx = i;
break;
}
}
console.log('Timeline grid item index:', timelineIdx);
// Get full timeline text before adding
if (timelineIdx >= 0) {
const text = (await gridItems.nth(timelineIdx).textContent() || '').trim();
console.log('Timeline text before add (first 500 chars):');
console.log(text.substring(0, 500));
console.log('...');
console.log('Contains Acropolis:', text.includes('Acropolis'));
console.log('Contains Requested:', text.includes('Requested'));
}
// Click Q to add Acropolis
const buttons = page.locator('.keyContainer > div > div');
const count = await buttons.count();
for (let i = 0; i < count; i++) {
const txt = (await buttons.nth(i).textContent()) || '';
if (txt.trim().toUpperCase().startsWith('Q')) {
await buttons.nth(i).click({ force: true });
console.log('Clicked Q at index', i);
break;
}
}
await page.waitForTimeout(2000);
// Check entity view
const evName = (await page.locator('.entityClickView #entityName').textContent() || '').trim();
console.log('EntityView name after add:', evName);
// Get timeline text after adding
if (timelineIdx >= 0) {
const text = (await gridItems.nth(timelineIdx).textContent() || '').trim();
console.log('Timeline text after add (first 500 chars):');
console.log(text.substring(0, 500));
console.log('Contains Acropolis:', text.includes('Acropolis'));
console.log('Contains Requested:', text.includes('Requested'));
console.log('Contains New:', text.includes('New'));
}
// Count Virtualize items in timeline
const virtualItems = page.locator('[style*="grid-template-columns: 1fr 1fr"]');
console.log('Virtualize items (grid items):', await virtualItems.count());
// Click Clear Build Order
await page.locator('button').filter({ hasText: 'Clear Build Order' }).click();
await page.waitForTimeout(2000);
// Check entity view after clear
const evAfterClear = await page.locator('.entityClickView #entityName').count();
console.log('EntityView #entityName count after clear:', evAfterClear);
if (evAfterClear > 0) {
console.log('EntityView name after clear:', (await page.locator('.entityClickView #entityName').textContent() || '').trim());
}
// Check timeline after clear
if (timelineIdx >= 0) {
const text = (await gridItems.nth(timelineIdx).textContent() || '').trim();
console.log('Timeline text after clear (first 300 chars):');
console.log(text.substring(0, 300));
console.log('Contains Acropolis:', text.includes('Acropolis'));
console.log('Virtualize items:', await virtualItems.count());
}
await browser.close();
})();
+64
View File
@@ -0,0 +1,64 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:5111/build-calculator', { timeout: 30000, waitUntil: 'networkidle' });
await page.waitForTimeout(10000);
const selects = page.locator('select');
await selects.nth(0).selectOption("Q'Rath");
await page.waitForTimeout(500);
await selects.nth(1).selectOption('Orzum');
await page.waitForTimeout(2000);
const gridItems = page.locator('.calculatorGrid > div');
const gCount = await gridItems.count();
for (let i = 0; i < gCount; i++) {
const txt = (await gridItems.nth(i).textContent() || '').trim();
const cls = await gridItems.nth(i).getAttribute('class');
console.log('=== Grid Item', i, 'class:', cls, '===');
console.log(txt.substring(0, 200));
console.log('---');
}
console.log('\n=== Clicking Q ===');
const buttons = page.locator('.keyContainer > div > div');
const count = await buttons.count();
for (let i = 0; i < count; i++) {
const txt = (await buttons.nth(i).textContent()) || '';
if (txt.trim().toUpperCase().startsWith('Q')) {
await buttons.nth(i).click({ force: true });
break;
}
}
await page.waitForTimeout(2000);
console.log('\n=== After Q click ===');
for (let i = 0; i < gCount; i++) {
const txt = (await gridItems.nth(i).textContent() || '').trim();
console.log('Grid', i, '- contains Acropolis:', txt.includes('Acropolis'));
if (txt.includes('Acropolis')) {
console.log(' Text around Acropolis:');
const idx = txt.indexOf('Acropolis');
console.log(' ', txt.substring(Math.max(0, idx - 40), idx + 40));
}
}
// Entity view
console.log('\nEntityView name:', (await page.locator('.entityClickView #entityName').textContent() || '').trim());
// Click Clear
await page.locator('button').filter({ hasText: 'Clear Build Order' }).click();
await page.waitForTimeout(2000);
console.log('\n=== After Clear ===');
for (let i = 0; i < gCount; i++) {
const txt = (await gridItems.nth(i).textContent() || '').trim();
console.log('Grid', i, '- contains Acropolis:', txt.includes('Acropolis'));
}
console.log('\nEntityView after clear count:', await page.locator('.entityClickView #entityName').count());
await browser.close();
})();
+101
View File
@@ -0,0 +1,101 @@
const ScreenType = Object.freeze({ Desktop: 'desktop', Tablet: 'tablet', Mobile: 'mobile' });
class Website {
constructor(page, options = {}) {
this.page = page;
this.screenType = ScreenType.Desktop;
this.runAgainstProduction = options.production || process.env.RUN_AGAINST_PRODUCTION === 'true';
if (this.runAgainstProduction) {
this.baseUrl = 'https://igpfanreference.ca';
} else {
const hook = process.env.TEST_HOOK || '';
this.deploymentType = hook.includes('localhost') ? 'Local' : 'Dev';
this.baseUrl = this.deploymentType === 'Dev'
? 'https://calm-mud-04916b210.1.azurestaticapps.net'
: 'https://localhost:7234';
}
const BuildCalculatorPage = require('../pages/buildCalculatorPage');
const HarassCalculatorPage = require('../pages/harassCalculator.page');
const DatabasePage = require('../pages/database.page');
const DatabaseSinglePage = require('../pages/databaseSingle.page');
const NavigationBar = require('../shared/navigationBar');
const WebsiteSearchDialog = require('../shared/websiteSearchDialog');
this.buildCalculatorPage = new BuildCalculatorPage(this);
this.harassCalculatorPage = new HarassCalculatorPage(this);
this.databasePage = new DatabasePage(this);
this.databaseSinglePage = new DatabaseSinglePage(this);
this.navigationBar = new NavigationBar(this);
this.websiteSearchDialog = new WebsiteSearchDialog(this);
}
locator(selector) {
return this.page.locator(selector);
}
find(byId) {
return this.page.locator(`#${byId}`);
}
findWithParent(byId, withParentId) {
return this.page.locator(`#${withParentId} #${byId}`);
}
findScreenSpecific(byId) {
return this.page.locator(`#${this.screenType}-${byId}`);
}
findAll(byId) {
return this.page.locator(`#${byId}`);
}
findAllWithTag(tag) {
return this.page.locator(tag);
}
findAllWithTagFromElement(element, tag) {
return element.locator(tag);
}
findButtonWithLabel(label) {
return this.page.locator(`button[label="${label}"]`);
}
findChildren(ofId, tagname) {
return this.page.locator(`#${ofId} ${tagname}`);
}
async findText(byId) {
return (await this.page.locator(`#${byId}`).textContent()) || '';
}
async findInt(byId) {
const text = await this.findText(byId);
return parseInt(text, 10);
}
async clickSearchBackground() {
await this.page.locator('#searchBackground').click();
}
async clickElement(element) {
await element.click();
}
async enterInput(element, value) {
await element.fill(String(value));
await element.press('Enter');
}
async goto(path) {
if (path) {
await this.page.goto(`${this.baseUrl}/${path}`);
} else {
await this.page.goto(this.baseUrl);
}
}
}
module.exports = { Website, ScreenType };
+76
View File
@@ -0,0 +1,76 @@
{
"name": "playwright",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "playwright",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@playwright/test": "^1.60.0",
"playwright": "^1.60.0"
}
},
"node_modules/@playwright/test": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.60.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.60.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}
+19
View File
@@ -0,0 +1,19 @@
{
"name": "playwright",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "npx playwright test",
"test:headed": "npx playwright test --headed",
"report": "npx playwright show-report"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@playwright/test": "^1.60.0",
"playwright": "^1.60.0"
}
}
+17
View File
@@ -0,0 +1,17 @@
class BasePage {
constructor(website) {
this.website = website;
}
get url() {
throw new Error('Subclasses must implement url');
}
async getLinks() {
const content = this.website.find('content');
const links = content.locator('a');
return await links.evaluateAll(els => els.map(el => el.getAttribute('href')).filter(Boolean));
}
}
module.exports = BasePage;
@@ -0,0 +1,52 @@
class ArmyComponent {
constructor(page) {
this.page = page;
}
armyView() {
return this.page.locator('.armyView');
}
displayValue(label) {
return this.page.locator('.displayContainer').filter({ hasText: label }).locator('.displayContent');
}
armyCards() {
return this.armyView().locator('.armyCard');
}
async getArmyCompletedAt() {
return await this.displayValue('Army Completed At').textContent();
}
async getArmyAttackingAt() {
return await this.displayValue('Army Attacking At').textContent();
}
async getArmyUnitNames() {
const cards = await this.armyCards().all();
const names = [];
for (const card of cards) {
const text = await card.innerText();
const match = text.match(/\d+x\s*(.+)/);
names.push(match ? match[1].trim() : text.trim());
}
return names;
}
async getArmyUnitCounts() {
const cards = await this.armyCards().all();
const counts = [];
for (const card of cards) {
const countEl = card.locator('.armyCount');
const nameEl = card.locator('div').last();
const count = await countEl.textContent();
const name = await nameEl.textContent();
const num = count ? parseInt(count.replace('x', ''), 10) : 0;
counts.push({ name: (name || '').trim(), count: num });
}
return counts;
}
}
module.exports = ArmyComponent;
@@ -0,0 +1,47 @@
class BankComponent {
constructor(page) {
this.page = page;
}
bankContainer() {
return this.page.locator('.bankContainer');
}
displayValue(label) {
return this.bankContainer().locator('.displayContainer').filter({ hasText: label }).locator('.displayContent');
}
async getTime() {
return await this.displayValue('Time').textContent();
}
async getAlloy() {
return await this.displayValue('Alloy').textContent();
}
async getEther() {
return await this.displayValue('Ether').textContent();
}
async getPyre() {
return await this.displayValue('Pyre').textContent();
}
async getSupply() {
return await this.displayValue('Supply').textContent();
}
async getWorkerCount() {
return await this.bankContainer().locator('.workerText').locator('.displayContent').nth(0).textContent();
}
async getBusyWorkerCount() {
return await this.bankContainer().locator('.workerText').locator('.displayContent').nth(1).textContent();
}
async getCreatingWorkerCount() {
return await this.bankContainer().locator('.workerText').locator('.displayContent').nth(2).textContent();
}
}
module.exports = BankComponent;
@@ -0,0 +1,35 @@
class BuildChartComponent {
constructor(page) {
this.page = page;
}
chartsContainer() {
return this.page.locator('.chartsContainer');
}
displayValue(label) {
return this.page.locator('.displayContainer').filter({ hasText: label }).locator('.displayContent');
}
async getHighestAlloy() {
return await this.displayValue('Highest Alloy').textContent();
}
async getHighestEther() {
return await this.displayValue('Highest Ether').textContent();
}
async getHighestPyre() {
return await this.displayValue('Highest Pyre').textContent();
}
async getHighestArmy() {
return await this.displayValue('Highest Army').textContent();
}
async getChartCount() {
return await this.chartsContainer().locator('> div').count();
}
}
module.exports = BuildChartComponent;
@@ -0,0 +1,15 @@
class BuildOrderComponent {
constructor(page) {
this.page = page;
}
jsonTextarea() {
return this.page.locator('textarea');
}
async getJsonData() {
return await this.jsonTextarea().inputValue();
}
}
module.exports = BuildOrderComponent;
@@ -0,0 +1,33 @@
class EntityClickViewComponent {
constructor(page) {
this.page = page;
}
entityClickView() {
return this.page.locator('.entityClickView');
}
async getEntityName() {
const el = this.entityClickView().locator('#entityName');
if ((await el.count()) === 0) return null;
return (await el.textContent()) || '';
}
async getEntityHealth() {
const healthText = this.entityClickView().locator('div').filter({ hasText: /Health/i }).first();
if ((await healthText.count()) === 0) return null;
const text = (await healthText.textContent()) || '';
const match = text.match(/(\d+)/);
return match ? match[1] : null;
}
async clickDetailedView() {
await this.entityClickView().locator('button').filter({ hasText: 'Detailed' }).click();
}
async clickPlainView() {
await this.entityClickView().locator('button').filter({ hasText: 'Plain' }).click();
}
}
module.exports = EntityClickViewComponent;
@@ -0,0 +1,35 @@
class FilterComponent {
constructor(page) {
this.page = page;
}
factionSelect() {
return this.page.locator('select').filter({ has: this.page.locator('option:has-text("Aru"), option:has-text("Q\'Rath")') });
}
immortalSelect() {
return this.page.locator('select').filter({ has: this.page.locator('option:has-text("Orzum"), option:has-text("Ajari"), option:has-text("Atzlan"), option:has-text("Mala"), option:has-text("Xol")') });
}
async selectFaction(faction) {
await this.factionSelect().selectOption(faction);
}
async selectImmortal(immortal) {
await this.immortalSelect().selectOption(immortal);
}
async getSelectedFaction() {
return await this.factionSelect().inputValue();
}
async getSelectedImmortal() {
return await this.immortalSelect().inputValue();
}
async getAvailableImmortals() {
return await this.immortalSelect().locator('option').allTextContents();
}
}
module.exports = FilterComponent;
@@ -0,0 +1,39 @@
class HighlightsComponent {
constructor(page) {
this.page = page;
}
highlightsContainer() {
return this.page.locator('.highlightsContainer');
}
requestedColumn() {
return this.highlightsContainer().locator('div').filter({ hasText: 'Requested' }).locator('+ div');
}
finishedColumn() {
return this.highlightsContainer().locator('div').filter({ hasText: 'Finished' }).locator('+ div');
}
async getRequestedItems() {
const items = await this.highlightsContainer().locator('div').filter({ hasText: /^\d+\s*\|/ }).all();
const result = [];
for (const item of items) {
const text = (await item.textContent()) || '';
result.push(text.trim());
}
return result;
}
async getFinishedItems() {
const items = await this.highlightsContainer().locator('div').filter({ hasText: /^\d+\s*\|/ }).all();
const result = [];
for (const item of items) {
const text = (await item.textContent()) || '';
result.push(text.trim());
}
return result;
}
}
module.exports = HighlightsComponent;
@@ -0,0 +1,50 @@
class HotkeyViewerComponent {
constructor(page) {
this.page = page;
}
keyContainer() {
return this.page.locator('.keyContainer');
}
async _findKeyButton(keyLabel) {
const upper = keyLabel.toUpperCase();
const buttons = this.keyContainer().locator('> div > div');
const count = await buttons.count();
for (let i = 0; i < count; i++) {
const btn = buttons.nth(i);
const text = (await btn.textContent()) || '';
if (text.trim().toUpperCase().startsWith(upper)) return btn;
}
return null;
}
async clickKey(keyText) {
const btn = await this._findKeyButton(keyText);
if (!btn) throw new Error(`Key "${keyText}" not found`);
await btn.click({ force: true });
}
async getFirstEntityName(keyText) {
const btn = await this._findKeyButton(keyText);
if (!btn) return null;
const entities = btn.locator('> div');
if ((await entities.count()) === 0) return null;
return (await entities.first().textContent()) || '';
}
async getEntityNamesOnKey(keyText) {
const btn = await this._findKeyButton(keyText);
if (!btn) return [];
const entities = btn.locator('> div');
const count = await entities.count();
const names = [];
for (let i = 0; i < count; i++) {
const text = (await entities.nth(i).textContent()) || '';
names.push(text.trim());
}
return names.filter(Boolean);
}
}
module.exports = HotkeyViewerComponent;
@@ -0,0 +1,68 @@
class OptionsComponent {
constructor(page) {
this.page = page;
}
buildingInputDelayInput() {
return this.formNumberInput('Building Input Delay');
}
waitTimeInput() {
return this.formNumberInput('Wait Time');
}
waitToInput() {
return this.formNumberInput('Wait To');
}
addWaitButton() {
return this.buttonWithLabel('Add Wait').first();
}
addWaitToButton() {
return this.buttonWithLabel('Add Wait').last();
}
formNumberInput(label) {
return this.page.locator(`.formNumberContainer`).filter({ hasText: label }).locator('input[type="number"]');
}
buttonWithLabel(label) {
return this.page.locator('button').filter({ hasText: label });
}
async setBuildingInputDelay(value) {
await this.buildingInputDelayInput().fill(String(value));
await this.buildingInputDelayInput().press('Enter');
}
async setWaitTime(value) {
await this.waitTimeInput().fill(String(value));
}
async setWaitTo(value) {
await this.waitToInput().fill(String(value));
}
async clickAddWait() {
await this.addWaitButton().click();
}
async clickAddWaitTo() {
await this.addWaitToButton().click();
}
async getBuildingInputDelay() {
return await this.buildingInputDelayInput().inputValue();
}
async getWaitTime() {
return await this.waitTimeInput().inputValue();
}
async getWaitTo() {
return await this.waitToInput().inputValue();
}
}
module.exports = OptionsComponent;
@@ -0,0 +1,16 @@
class TimelineComponent {
constructor(page) {
this.page = page;
}
container() {
return this.page.locator('.calculatorGrid > div').filter({ hasText: 'Timeline highlights' });
}
async containsEntity(name) {
const text = (await this.container().textContent()) || '';
return text.includes(name);
}
}
module.exports = TimelineComponent;
@@ -0,0 +1,37 @@
class TimingComponent {
constructor(website) {
this.website = website;
}
attackTimeInput() {
return this.formNumberInput('Attack Time');
}
travelTimeInput() {
return this.formNumberInput('Travel Time');
}
formNumberInput(label) {
return this.website.locator(`.formNumberContainer`).filter({ hasText: label }).locator('input[type="number"]');
}
async setAttackTime(value) {
await this.attackTimeInput().fill(String(value));
await this.attackTimeInput().press('Enter');
}
async setTravelTime(value) {
await this.travelTimeInput().fill(String(value));
await this.travelTimeInput().press('Enter');
}
async getAttackTime() {
return await this.attackTimeInput().inputValue();
}
async getTravelTime() {
return await this.travelTimeInput().inputValue();
}
}
module.exports = TimingComponent;
+56
View File
@@ -0,0 +1,56 @@
const TimingComponent = require('./buildCalculator/timingComponent');
const FilterComponent = require('./buildCalculator/filterComponent');
const OptionsComponent = require('./buildCalculator/optionsComponent');
const BankComponent = require('./buildCalculator/bankComponent');
const ArmyComponent = require('./buildCalculator/armyComponent');
const HighlightsComponent = require('./buildCalculator/highlightsComponent');
const BuildOrderComponent = require('./buildCalculator/buildOrderComponent');
const TimelineComponent = require('./buildCalculator/timelineComponent');
const HotkeyViewerComponent = require('./buildCalculator/hotkeyViewerComponent');
const EntityClickViewComponent = require('./buildCalculator/entityClickViewComponent');
const BuildChartComponent = require('./buildCalculator/buildChartComponent');
const ToastComponent = require('../shared/toastComponent');
const BasePage = require('./base.page');
class BuildCalculatorPage extends BasePage {
constructor(website) {
super(website);
this.timing = new TimingComponent(website);
this.filter = new FilterComponent(website);
this.options = new OptionsComponent(website);
this.bank = new BankComponent(website);
this.army = new ArmyComponent(website);
this.highlights = new HighlightsComponent(website);
this.buildOrder = new BuildOrderComponent(website);
this.timeline = new TimelineComponent(website);
this.hotkeys = new HotkeyViewerComponent(website);
this.entityView = new EntityClickViewComponent(website);
this.chart = new BuildChartComponent(website);
this.toast = new ToastComponent(website);
}
get url() {
return 'build-calculator';
}
calculatorGrid() {
return this.website.locator('.calculatorGrid');
}
clearBuildOrderButton() {
return this.website.locator('button').filter({ hasText: 'Clear Build Order' });
}
async clickClearBuildOrder() {
await this.clearBuildOrderButton().click();
}
async goto() {
await this.website.goto(this.url);
return this;
}
}
module.exports = BuildCalculatorPage;
+27
View File
@@ -0,0 +1,27 @@
const BasePage = require('./base.page');
class DatabasePage extends BasePage {
get url() { return 'database'; }
async filterName(name) {
await this.website.enterInput(this.website.findAll('filterName').first(), name);
return this;
}
async getEntityName(entityType, entityName) {
return await this.website
.findWithParent('entityName', `${entityType.toLowerCase()}-${entityName.toLowerCase()}`)
.innerText();
}
async getEntityNameByIndex(index) {
return await this.website.findAll('entityName').nth(index).innerText();
}
async goto() {
await this.website.goto(this.url);
return this;
}
}
module.exports = DatabasePage;
+28
View File
@@ -0,0 +1,28 @@
const BasePage = require('./base.page');
class DatabaseSinglePage extends BasePage {
get url() { return 'database'; }
async getEntityName() {
return await this.website.find('entityName').innerText();
}
async getEntityHealth() {
return await this.website.find('entityHealth').innerText();
}
async getInvalidSearch() {
return await this.website.find('invalidSearch').innerText();
}
async getValidSearch() {
return await this.website.find('validSearch').innerText();
}
async goto(searchText) {
await this.website.goto(`${this.url}/${searchText}`);
return this;
}
}
module.exports = DatabaseSinglePage;
+68
View File
@@ -0,0 +1,68 @@
const BasePage = require('./base.page');
class HarassCalculatorPage extends BasePage {
get url() { return 'harass-calculator'; }
async setWorkersLostToHarass(number) {
await this.website.enterInput(this.website.find('numberOfWorkersLostToHarass'), number);
return this;
}
async setNumberOfTownHallsExisting(number) {
await this.website.enterInput(this.website.find('numberOfTownHallsExisting'), number);
return this;
}
async setTownHallTravelTime(forTownHall, number) {
const inputs = this.website.findChildren('numberOfTownHallTravelTimes', 'input');
await this.website.enterInput(inputs.nth(forTownHall), number);
return this;
}
async getTotalAlloyHarassment() {
return await this.website.findInt('totalAlloyHarassment');
}
async getWorkerReplacementCost() {
return await this.website.findInt('workerReplacementCost');
}
async getDelayedMiningCost() {
return await this.website.findInt('delayedMiningCost');
}
async getAverageTravelTime() {
return await this.website.findInt('getAverageTravelTime');
}
async getExampleTotalAlloyLoss() {
return await this.website.findInt('exampleTotalAlloyLoss');
}
async getExampleWorkerCost() {
return await this.website.findInt('exampleWorkerCost');
}
async getExampleMiningTimeCost() {
return await this.website.findInt('exampleMiningTimeCost');
}
async getExampleTotalAlloyLossAccurate() {
return await this.website.findInt('exampleTotalAlloyLossAccurate');
}
async getExampleTotalAlloyLossDifference() {
return await this.website.findInt('exampleTotalAlloyLossDifference');
}
async getExampleTotalAlloyLossAccurateDifference() {
return await this.website.findInt('exampleTotalAlloyLossAccurateDifference');
}
async goto() {
await this.website.goto(this.url);
return this;
}
}
module.exports = HarassCalculatorPage;
+15
View File
@@ -0,0 +1,15 @@
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: './tests',
fullyParallel: true,
retries: 1,
timeout: 30000,
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
],
});
+19
View File
@@ -0,0 +1,19 @@
class NavigationBar {
constructor(website) {
this.website = website;
}
get searchButton() { return this.website.findScreenSpecific('searchButton'); }
async clickHomeLink() {
await this.website.clickElement(this.website.locator('a:has-text("IGP Fan Reference")'));
return this;
}
async clickSearchButton() {
await this.website.clickElement(this.searchButton);
return this.website.websiteSearchDialog;
}
}
module.exports = NavigationBar;
+40
View File
@@ -0,0 +1,40 @@
class ToastComponent {
constructor(page) {
this.page = page;
}
container() {
return this.page.locator('.toastsContainer');
}
toasts() {
return this.page.locator('.toastsContainer .toastContainer');
}
async getToastTitles() {
const titles = await this.page.locator('.toastsContainer .toastTitle').allTextContents();
return titles.map(t => t.trim()).filter(Boolean);
}
_page() {
return this.page.page || this.page;
}
async hasToastContaining(text) {
try {
await this._page().waitForFunction(
(expected) => {
const titles = document.querySelectorAll('.toastsContainer .toastTitle');
return Array.from(titles).some(t => t.textContent.trim().includes(expected));
},
text,
{ timeout: 3000 }
);
return true;
} catch {
return false;
}
}
}
module.exports = ToastComponent;
+25
View File
@@ -0,0 +1,25 @@
class WebsiteSearchDialog {
constructor(website) {
this.website = website;
}
get searchBackground() { return this.website.find('searchBackground'); }
get searchInput() { return this.website.find('searchInput'); }
async closeDialog() {
await this.website.clickSearchBackground();
return this.website.navigationBar;
}
async search(text) {
await this.website.enterInput(this.searchInput, text);
return this;
}
async selectSearchEntity(label) {
await this.website.clickElement(this.website.findButtonWithLabel(label));
return this.website.databaseSinglePage;
}
}
module.exports = WebsiteSearchDialog;
+125
View File
@@ -0,0 +1,125 @@
const { test, expect } = require('@playwright/test');
const BuildCalculatorPage = require('../pages/buildCalculatorPage');
const { Website } = require('../helpers/website');
test.describe('Build Calculator', () => {
let website;
test.beforeEach(({ page }) => {
website = new Website(page);
});
test('Add entities via keyboard Q, W, E with Q\'Rath/Orzum', async ({ page }) => {
const calc = website.buildCalculatorPage;
await calc.goto();
await calc.filter.selectFaction("Q'Rath");
await calc.filter.selectImmortal('Orzum');
await calc.hotkeys.clickKey('TAB');
const keyNames = { Q: 'q', W: 'w', E: 'e', TAB: 'Tab' };
for (const key of ['Q', 'W', 'E', 'TAB']) {
const entityNames = await calc.hotkeys.getEntityNamesOnKey(key);
if (entityNames.length === 0) continue;
await page.keyboard.press(keyNames[key]);
const viewName = await calc.entityView.getEntityName();
expect(viewName).toBeTruthy();
expect(entityNames).toContain(viewName);
}
});
test('Add entities via hotkeys TAB, Q, W, E with Q\'Rath/Orzum', async ({ page }) => {
const calc = website.buildCalculatorPage;
await calc.goto();
await calc.filter.selectFaction("Q'Rath");
await calc.filter.selectImmortal('Orzum');
for (const key of ['TAB', 'Q', 'W', 'E']) {
const entityNames = await calc.hotkeys.getEntityNamesOnKey(key);
if (entityNames.length === 0) continue;
await calc.hotkeys.clickKey(key);
const viewName = await calc.entityView.getEntityName();
expect(viewName).toBeTruthy();
expect(entityNames).toContain(viewName);
}
});
test('Add Acropolis via Q, verify entity view and timeline, then clear', async ({ page }) => {
const calc = website.buildCalculatorPage;
await calc.goto();
const buttons = page.locator('.keyContainer > div > div');
console.log('Initial Q button text:', await buttons.filter({ hasText: /^Q/ }).first().textContent());
// Wait for Blazor re-render to complete by waiting for the button text to stabilize
// (it goes from QAcropolis → empty during re-render → back to QAcropolis)
let tries = 0;
let text = '';
while (tries < 20) {
await page.waitForTimeout(500);
try {
text = (await buttons.filter({ hasText: /^Q/ }).first().textContent() || '').trim();
if (text && text.length > 1) break;
} catch { }
tries++;
}
console.log(`After Blazor render (${(tries+1)*0.5}s): Q button text: ${JSON.stringify(text)}`);
await calc.filter.selectFaction("Q'Rath");
await calc.filter.selectImmortal('Orzum');
await page.waitForTimeout(1000);
console.log('After filter Q button text:', await buttons.filter({ hasText: /^Q/ }).first().textContent());
expect(await calc.timeline.containsEntity('Acropolis')).toBe(false);
await calc.hotkeys.clickKey('Q');
await page.waitForTimeout(1000);
expect(await calc.entityView.getEntityName()).toBe('Acropolis');
expect(await calc.timeline.containsEntity('Acropolis')).toBe(true);
await calc.clickClearBuildOrder();
await page.waitForTimeout(1000);
expect(await calc.timeline.containsEntity('Acropolis')).toBe(false);
expect(await calc.entityView.getEntityName()).toBeNull();
});
test('Missing Requirements toast when building Soul Foundry without Legion Hall', async ({ page }) => {
const calc = website.buildCalculatorPage;
await calc.goto();
await calc.filter.selectFaction("Q'Rath");
await calc.filter.selectImmortal('Orzum');
await calc.hotkeys.clickKey('E');
const hasToast = await calc.toast.hasToastContaining('Missing Requirements');
expect(hasToast).toBe(true);
});
test('Not Enough Ether toast when building Soul Foundry after Legion Hall', async ({ page }) => {
const calc = website.buildCalculatorPage;
await calc.goto();
await calc.filter.selectFaction("Q'Rath");
await calc.filter.selectImmortal('Orzum');
await calc.hotkeys.clickKey('W');
await calc.hotkeys.clickKey('E');
const hasToast = await calc.toast.hasToastContaining('Not Enough Ether');
expect(hasToast).toBe(true);
});
});
+32
View File
@@ -0,0 +1,32 @@
const { test, expect } = require('@playwright/test');
const { Website } = require('../helpers/website');
test.describe('Harass Calculator', () => {
let website;
test.beforeEach(({ page }) => {
website = new Website(page);
});
test('CalculatorInput', async () => {
const page = website.harassCalculatorPage;
await page.goto();
await page.setWorkersLostToHarass(3);
await page.setNumberOfTownHallsExisting(2);
await page.setTownHallTravelTime(0, 30);
const result = await page.getTotalAlloyHarassment();
expect(result).toBe(240);
});
test('CalculatedExampleInformation', async () => {
const page = website.harassCalculatorPage;
await page.goto();
expect(await page.getExampleTotalAlloyLoss()).toBe(720);
expect(await page.getExampleWorkerCost()).toBe(300);
expect(await page.getExampleMiningTimeCost()).toBe(420);
expect(await page.getExampleTotalAlloyLossAccurate()).toBe(450);
expect(await page.getExampleTotalAlloyLossDifference()).toBe(300);
expect(await page.getExampleTotalAlloyLossAccurateDifference()).toBe(270);
});
});
+28
View File
@@ -0,0 +1,28 @@
const { test } = require('@playwright/test');
const { Website } = require('../helpers/website');
const TestReport = require('../utils/testReport');
test.describe('Link Verification', () => {
let website;
let testReport;
test.beforeEach(() => {
testReport = new TestReport();
});
test('VerifyPageLinks', async ({ page }) => {
website = new Website(page);
testReport.createTest(test.info().title);
await website.harassCalculatorPage.goto();
await testReport.verifyLinks(website.harassCalculatorPage);
await website.databasePage.goto();
await testReport.verifyLinks(website.databasePage);
await website.databaseSinglePage.goto('throne');
await testReport.verifyLinks(website.databaseSinglePage);
testReport.throwErrors();
});
});
+54
View File
@@ -0,0 +1,54 @@
const { test, expect } = require('@playwright/test');
const { Website } = require('../helpers/website');
test.describe('Search Features', () => {
let website;
test.beforeEach(({ page }) => {
website = new Website(page);
});
test('DesktopOpenCloseSearchDialog', async () => {
await website.goto();
await website.navigationBar.clickSearchButton();
await website.websiteSearchDialog.closeDialog();
await website.navigationBar.clickHomeLink();
});
test('DesktopSearchForThrone', async () => {
await website.goto();
await website.navigationBar.clickSearchButton();
await website.websiteSearchDialog.search('Throne');
const page = await website.websiteSearchDialog.selectSearchEntity('Throne');
const name = await page.getEntityName();
const health = await page.getEntityHealth();
expect(name).toBe('Throne');
expect(health.trim()).not.toBe('');
});
test('DesktopFilterForThrone', async () => {
const page = website.databasePage;
await page.goto();
await page.filterName('Throne');
const name = await page.getEntityNameByIndex(0);
expect(name).toBe('Throne');
});
test('SeeThroneByDefault', async () => {
const page = website.databasePage;
await page.goto();
const name = await page.getEntityName('army', 'throne');
expect(name).toBe('Throne');
});
test('DirectLinkNotThroneFailure', async () => {
const page = website.databaseSinglePage;
await page.goto('not throne');
const invalidSearch = await page.getInvalidSearch();
const validSearch = await page.getValidSearch();
expect(invalidSearch).toBe('not throne');
expect(validSearch).toBe('Throne');
});
});
+77
View File
@@ -0,0 +1,77 @@
class TestReport {
constructor() {
this.tests = [];
}
createTest(name) {
const test = { name, result: true, messages: [] };
this.tests.push(test);
return test;
}
throwErrors() {
const latest = this.tests[this.tests.length - 1];
if (!latest.result) {
const msgs = latest.messages.map(m => m.description).join('\n');
throw new Error(`${latest.name} test failed with ${latest.messages.length} messages.\n\n${msgs}`);
}
}
checkPassed(passed, message) {
if (!passed) {
const latest = this.tests[this.tests.length - 1];
latest.result = false;
latest.messages.push(message);
}
}
async verifyLinks(page) {
const links = await page.getLinks();
for (const link of links) {
if (link.startsWith('mailto')) continue;
try {
const response = await fetch(link);
if (!response.ok) {
this.checkPassed(false, {
color: 'red',
title: 'Bad Link',
description: `${link} failed on page ${page.url} with status code ${response.status}`
});
}
} catch (e) {
this.checkPassed(false, {
color: 'red',
title: 'Bad Link',
description: `${link} failed on page ${page.url} with error ${e.message}`
});
}
}
}
didTestsPass() {
return this.tests.every(t => t.result);
}
getMessages() {
if (this.didTestsPass()) {
return [{
title: 'Passed',
color: 0x00FF00,
description: `All ${this.tests.length} tests passed.`
}];
}
const messages = [];
for (const test of this.tests) {
for (const msg of test.messages) {
messages.push({
title: msg.title,
color: parseInt(msg.color, 16),
description: msg.description
});
}
}
return messages;
}
}
module.exports = TestReport;