143 lines
4.0 KiB
TypeScript
143 lines
4.0 KiB
TypeScript
import { Page, Locator, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Page Object Model for Challenge Detail page
|
|
* URL: /chal/{id}
|
|
*/
|
|
export class ChallengePage {
|
|
readonly page: Page;
|
|
readonly verdictBadge: Locator;
|
|
readonly runtimeText: Locator;
|
|
readonly memoryText: Locator;
|
|
readonly scoreText: Locator;
|
|
readonly codeBlock: Locator;
|
|
readonly subtaskResults: Locator;
|
|
readonly testdataResults: Locator;
|
|
readonly messageText: Locator;
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
|
|
// Challenge result elements
|
|
this.verdictBadge = page.locator('.badge, [class*="verdict"], [class*="state"]').first();
|
|
this.runtimeText = page.locator('text=/Runtime|執行時間/');
|
|
this.memoryText = page.locator('text=/Memory|記憶體/');
|
|
this.scoreText = page.locator('text=/Score|分數|Rate/');
|
|
this.codeBlock = page.locator('pre, code, .code-block');
|
|
this.subtaskResults = page.locator('[class*="subtask"]');
|
|
this.testdataResults = page.locator('[class*="testdata"]');
|
|
this.messageText = page.locator('.message, [class*="response"]');
|
|
}
|
|
|
|
/**
|
|
* Navigate to a specific challenge page
|
|
*/
|
|
async goto(challengeId: number) {
|
|
await this.page.goto(`/chal/${challengeId}`);
|
|
await this.page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
/**
|
|
* Get the verdict/state of the challenge
|
|
*/
|
|
async getVerdict(): Promise<string> {
|
|
// Wait for verdict to be not "Challenging" or "Not Started"
|
|
await this.page.waitForTimeout(1000); // Initial wait
|
|
|
|
const verdictElement = this.verdictBadge.or(
|
|
this.page.locator('text=/AC|WA|TLE|MLE|RE|CE|PE|IE/')
|
|
).first();
|
|
|
|
if (await verdictElement.count() === 0) {
|
|
return 'UNKNOWN';
|
|
}
|
|
|
|
const verdictText = await verdictElement.textContent();
|
|
return verdictText?.trim() || 'UNKNOWN';
|
|
}
|
|
|
|
/**
|
|
* Wait for challenge to finish judging
|
|
* @param timeoutMs Maximum time to wait in milliseconds
|
|
* @param pollIntervalMs How often to check the verdict
|
|
*/
|
|
async waitForJudgeComplete(timeoutMs: number = 60000, pollIntervalMs: number = 2000) {
|
|
const startTime = Date.now();
|
|
|
|
while (Date.now() - startTime < timeoutMs) {
|
|
const verdict = await this.getVerdict();
|
|
|
|
// Check if judging is complete
|
|
if (verdict !== 'Challenging' && verdict !== 'Not Started' && verdict !== 'UNKNOWN') {
|
|
return verdict;
|
|
}
|
|
|
|
// Wait before next poll
|
|
await this.page.waitForTimeout(pollIntervalMs);
|
|
|
|
// Reload the page to get updated status
|
|
await this.page.reload({ waitUntil: 'networkidle' });
|
|
}
|
|
|
|
throw new Error(`Challenge did not complete within ${timeoutMs}ms`);
|
|
}
|
|
|
|
/**
|
|
* Get runtime and memory usage
|
|
*/
|
|
async getPerformanceMetrics() {
|
|
const metrics = {
|
|
runtime: '',
|
|
memory: '',
|
|
};
|
|
|
|
if (await this.runtimeText.isVisible()) {
|
|
const runtimeFull = await this.runtimeText.textContent();
|
|
metrics.runtime = runtimeFull?.match(/\d+/)?.[0] || '';
|
|
}
|
|
|
|
if (await this.memoryText.isVisible()) {
|
|
const memoryFull = await this.memoryText.textContent();
|
|
metrics.memory = memoryFull?.match(/\d+/)?.[0] || '';
|
|
}
|
|
|
|
return metrics;
|
|
}
|
|
|
|
/**
|
|
* Get the score/rate
|
|
*/
|
|
async getScore(): Promise<string> {
|
|
if (await this.scoreText.isVisible()) {
|
|
const scoreText = await this.scoreText.textContent();
|
|
const scoreMatch = scoreText?.match(/\d+/);
|
|
return scoreMatch?.[0] || '0';
|
|
}
|
|
return '0';
|
|
}
|
|
|
|
/**
|
|
* Verify we are on the challenge page
|
|
*/
|
|
async verifyOnPage(challengeId: number) {
|
|
await expect(this.page).toHaveURL(new RegExp(`/chal/${challengeId}`));
|
|
}
|
|
|
|
/**
|
|
* Check if subtask results are visible
|
|
*/
|
|
async hasSubtaskResults(): Promise<boolean> {
|
|
return await this.subtaskResults.count() > 0;
|
|
}
|
|
|
|
/**
|
|
* Get challenge message (for errors, etc.)
|
|
*/
|
|
async getMessage(): Promise<string | null> {
|
|
if (await this.messageText.isVisible()) {
|
|
return await this.messageText.textContent();
|
|
}
|
|
return null;
|
|
}
|
|
}
|