import { Page, Locator } from '@playwright/test'; export class DP2FormPage { readonly page: Page; // Form fields readonly playerNameInput: Locator; readonly currentPointsInput: Locator; readonly crimeCategoryButtons: { [key: string]: Locator }; readonly specificOffenseSelectTrigger: Locator; // Crime details sections readonly offenseDetailsSection: Locator; // Special item inputs (for theft) readonly specialItems: { elytra: Locator; netherStar: Locator; beacon: Locator; netheriteBlock: Locator; diamondBlock: Locator; }; // Additional item inputs readonly additionalItemInputs: Locator; // Block/Entity count inputs readonly blockCountInput: Locator; readonly entityCountInput: Locator; // SPP checkbox readonly sppCheckbox: Locator; // Submit button readonly calculateButton: Locator; // Results section readonly resultsSection: Locator; readonly commandsList: Locator; readonly summarySection: Locator; readonly explanationTextarea: Locator; readonly resetButton: Locator; constructor(page: Page) { this.page = page; // Form fields this.playerNameInput = page.locator('input[id="playerName"]'); this.currentPointsInput = page.locator('input[id="currentPoints"]'); // Crime category buttons this.crimeCategoryButtons = { all: page.locator('button').filter({ hasText: 'all' }), item: page.locator('button').filter({ hasText: 'item offenses' }), block: page.locator('button').filter({ hasText: 'block offenses' }), hacking: page.locator('button').filter({ hasText: 'hacking offenses' }), communication: page.locator('button').filter({ hasText: 'communication offenses' }), }; this.specificOffenseSelectTrigger = page.locator('button').filter({ hasText: 'select an offense' }); // Crime details this.offenseDetailsSection = page.locator('h3').filter({ hasText: 'offense details' }).locator('..'); // Special items this.specialItems = { elytra: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="elytra (20 points)"]') }), netherStar: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="nether star (20 points)"]') }), beacon: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="beacon (20 points)"]') }), netheriteBlock: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="netherite block (10 points)"]') }), diamondBlock: page.locator('input').filter({ has: page.locator('xpath=preceding-sibling::span[text()="diamond block (10 points)"]') }), }; // Additional items this.additionalItemInputs = page.locator('label').filter({ hasText: 'additional items stolen' }).locator('..'); // Block/Entity inputs this.blockCountInput = page.locator('input[id="blockCount"]'); this.entityCountInput = page.locator('input[id="entityCount"]'); // SPP checkbox this.sppCheckbox = page.locator('input[id="isSPP"]'); // Submit button this.calculateButton = page.locator('button[type="submit"]'); // Results this.resultsSection = page.locator('h3').filter({ hasText: 'results' }).locator('..').locator('..'); this.commandsList = this.resultsSection.locator('label').filter({ hasText: 'commands' }).locator('..'); this.summarySection = this.resultsSection.locator('label').filter({ hasText: 'summary' }).locator('..'); this.explanationTextarea = this.resultsSection.locator('textarea'); this.resetButton = this.resultsSection.locator('button').filter({ hasText: 'reset form' }); } async goto() { await this.page.goto('/'); } async fillPlayerInfo(playerName: string, currentPoints: number = 0) { await this.playerNameInput.fill(playerName); await this.currentPointsInput.fill(currentPoints.toString()); } async selectCrimeCategory(category: 'all' | 'item' | 'block' | 'hacking' | 'communication') { await this.crimeCategoryButtons[category].click(); } async selectSpecificOffense(offenseId: string) { // Click the select trigger to open dropdown await this.specificOffenseSelectTrigger.click(); // Find the crime name from the CRIMES array (we need to import this or map it) const crimeName = this.getCrimeNameFromId(offenseId); // Click the option with the matching text (exact match) await this.page.locator('[data-slot="select-content"]').locator('text=' + crimeName).first().click(); } private getCrimeNameFromId(crimeId: string): string { // This is a simplified mapping - in production, you'd want to import the CRIMES array const crimeMap: { [key: string]: string } = { 'inappropriate_item_names': 'Inappropriate Item Names', 'inappropriate_book_contents': 'Inappropriate Book Contents', 'theft': 'Theft', 'unconsensual_killing': 'Unconsensual Killing', 'illegal_item_use': 'Using Illegal Item', 'vandalism': 'Vandalism', 'grief': 'Grief', 'theft_grief': 'Theft-Grief', 'vandalism_infrastructure': 'Vandalism of Public Infrastructure', 'trespassing': 'Trespassing', 'trespassing_staff': 'Trespassing on Staff/SPP Land', 'x_raying': 'X-Raying', 'hacking_client': 'Use of Hacking Client', 'lagging_server': 'Deliberately Lagging Server', 'worldedit_misuse': 'Misuse of Worldedit', 'exploit_abuse': 'Abuse of Exploits', 'abusive_chat': 'Abusive Chat', 'inciting_verbal_conflict': 'Inciting Verbal Conflict', 'abusive_vc': 'Abusive VC Language', 'lying_to_staff': 'Lying to Staff Member', 'manipulation': 'Manipulation', 'grand_manipulation': 'Grand Manipulation', 'slander': 'Slander (Against SPP Only)', 'violation_nca': 'Violation of NCA', }; return crimeMap[crimeId] || crimeId; } async fillSpecialItems(items: { elytra?: number; netherStar?: number; beacon?: number; netheriteBlock?: number; diamondBlock?: number }) { if (items.elytra !== undefined) await this.specialItems.elytra.fill(items.elytra.toString()); if (items.netherStar !== undefined) await this.specialItems.netherStar.fill(items.netherStar.toString()); if (items.beacon !== undefined) await this.specialItems.beacon.fill(items.beacon.toString()); if (items.netheriteBlock !== undefined) await this.specialItems.netheriteBlock.fill(items.netheriteBlock.toString()); if (items.diamondBlock !== undefined) await this.specialItems.diamondBlock.fill(items.diamondBlock.toString()); } async addAdditionalItem(type: string, quantity: number) { // Click "add item" button const addButton = this.additionalItemInputs.locator('button').filter({ hasText: 'add item' }); await addButton.click(); // Fill in the last added item row const itemRows = this.additionalItemInputs.locator('div.flex.items-center.gap-2'); const lastRow = itemRows.last(); const inputs = lastRow.locator('input'); await inputs.nth(0).fill(type); await inputs.nth(1).fill(quantity.toString()); } async fillBlockCount(count: number) { await this.blockCountInput.fill(count.toString()); } async fillEntityCount(count: number) { await this.entityCountInput.fill(count.toString()); } async setSPP(isSPP: boolean) { if (isSPP) { await this.sppCheckbox.check(); } else { await this.sppCheckbox.uncheck(); } } async calculatePunishment() { await this.calculateButton.click(); } async resetForm() { await this.resetButton.click(); } async waitForResults() { await this.resultsSection.waitFor({ state: 'visible' }); } async getResults() { const commands = await this.commandsList.locator('code').allTextContents(); const summary = await this.summarySection.locator('div.text-sm.space-y-1').textContent(); const explanation = await this.explanationTextarea.inputValue(); return { commands, summary, explanation, }; } async getSummaryValues() { const summaryDiv = this.summarySection.locator('div.text-sm.space-y-1'); const lines = await summaryDiv.locator('div').allTextContents(); const values: { [key: string]: string } = {}; for (const line of lines) { const [key, value] = line.split(': '); values[key] = value; } return values; } }