Playwright start
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
node_modules/
|
||||
test-results/
|
||||
playwright-report/
|
||||
@@ -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); });
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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); });
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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();
|
||||
})();
|
||||
@@ -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 };
|
||||
Generated
+76
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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' } },
|
||||
],
|
||||
});
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user