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
+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;