367 lines
14 KiB
TypeScript
367 lines
14 KiB
TypeScript
import { Given, When, Then, expect } from '../fixtures/bdd-fixtures';
|
|
import { Quest } from '../types';
|
|
|
|
/**
|
|
* Create a quest via the API (faster than UI interaction).
|
|
* User must already be authenticated via the Background step.
|
|
*/
|
|
async function createQuestViaApi(page: any, baseUrl: string, questData: {
|
|
title: string;
|
|
description: string;
|
|
status?: string;
|
|
quest_type?: string;
|
|
priority?: string;
|
|
is_dark_magic?: boolean;
|
|
location_id?: number;
|
|
}): Promise<number | undefined> {
|
|
const statusMap: Record<string, string> = {
|
|
'Not Yet Begun': 'not_yet_begun',
|
|
'The Road Goes Ever On...': 'the_road_goes_ever_on',
|
|
'It Is Done': 'it_is_done',
|
|
'The Shadow Falls': 'the_shadow_falls',
|
|
};
|
|
const response = await page.request.post(`${baseUrl}/api/quests`, {
|
|
data: {
|
|
title: questData.title,
|
|
description: questData.description,
|
|
status: statusMap[questData.status || 'Not Yet Begun'] || questData.status || 'not_yet_begun',
|
|
quest_type: questData.quest_type || 'The Journey',
|
|
priority: questData.priority || 'Standard',
|
|
is_dark_magic: questData.is_dark_magic || false,
|
|
location_id: questData.location_id,
|
|
},
|
|
});
|
|
if (response.ok()) {
|
|
const data = await response.json();
|
|
return data.id;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
/**
|
|
* Get location ID by name via the API.
|
|
*/
|
|
async function getLocationIdByName(page: any, baseUrl: string, name: string): Promise<number | undefined> {
|
|
const response = await page.request.get(`${baseUrl}/api/locations`);
|
|
if (response.ok()) {
|
|
const locations: Array<{ id: number; name: string }> = await response.json();
|
|
const found = locations.find(l => l.name.toLowerCase().includes(name.toLowerCase()));
|
|
return found?.id;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
Given('I navigate to the quests page', async ({ page }) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
// Clean up accumulated test quests before each scenario to prevent DB bloat / slow loads
|
|
try {
|
|
await page.request.post(`${baseUrl}/api/test/cleanup`);
|
|
} catch {
|
|
// Cleanup endpoint may not be available — continue
|
|
}
|
|
await questsPage.goto();
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Given('I have a quest titled {string}', async ({ page }, questTitle: string) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const id = await createQuestViaApi(page, baseUrl, {
|
|
title: questTitle,
|
|
description: `Test quest: ${questTitle}`,
|
|
status: 'Not Yet Begun',
|
|
quest_type: 'The Journey',
|
|
});
|
|
(testContext as any).lastCreatedQuestTitle = questTitle;
|
|
(testContext as any).lastCreatedQuestId = id;
|
|
// Reload the quests page to see the new quest
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
Given('I have multiple quests with different statuses', async ({ page }) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
await createQuestViaApi(page, baseUrl, { title: 'Completed Quest 1', description: 'Done', status: 'It Is Done', quest_type: 'The Ring' });
|
|
await createQuestViaApi(page, baseUrl, { title: 'Completed Quest 2', description: 'Done', status: 'It Is Done', quest_type: 'The Journey' });
|
|
await createQuestViaApi(page, baseUrl, { title: 'In Progress Quest', description: 'WIP', status: 'The Road Goes Ever On...', quest_type: 'The Battle' });
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
Given('I have quests of different types', async ({ page }) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
await createQuestViaApi(page, baseUrl, { title: 'Journey Quest BDD', description: 'A long journey', quest_type: 'The Journey' });
|
|
await createQuestViaApi(page, baseUrl, { title: 'Battle Quest BDD', description: 'A great battle', quest_type: 'The Battle' });
|
|
await createQuestViaApi(page, baseUrl, { title: 'Ring Quest BDD', description: 'The One Ring', quest_type: 'The Ring' });
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
Given('I have quests in different locations', async ({ page }) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const mordorId = await getLocationIdByName(page, baseUrl, 'Mordor');
|
|
const rivendellId = await getLocationIdByName(page, baseUrl, 'Rivendell');
|
|
await createQuestViaApi(page, baseUrl, { title: 'Mordor Quest BDD', description: 'Darkness falls', quest_type: 'The Ring', location_id: mordorId });
|
|
await createQuestViaApi(page, baseUrl, { title: 'Rivendell Quest BDD', description: 'Elven halls', quest_type: 'The Journey', location_id: rivendellId });
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
Given('I have an incomplete quest titled {string}', async ({ page }, questTitle: string) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const id = await createQuestViaApi(page, baseUrl, {
|
|
title: questTitle,
|
|
description: `Incomplete quest: ${questTitle}`,
|
|
status: 'Not Yet Begun',
|
|
quest_type: 'The Journey',
|
|
});
|
|
(testContext as any).lastCreatedQuestTitle = questTitle;
|
|
(testContext as any).lastCreatedQuestId = id;
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
Given('a quest completion mini-game is displayed', async ({ page }) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
// Create a quest via API and trigger completion via UI
|
|
const questTitle = `Mini-game Quest ${Date.now()}`;
|
|
await createQuestViaApi(page, baseUrl, { title: questTitle, description: 'For mini-game test', status: 'Not Yet Begun' });
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(1000); // Wait for quest list to reload
|
|
// Click the specifically created quest (not necessarily the first one)
|
|
const questTitles = await questsPage.getQuestTitles();
|
|
// Find our created quest title (partial match in case list is truncated)
|
|
const target = questTitles.find(t => t.includes('Mini-game Quest')) || questTitles[0];
|
|
if (target) {
|
|
await questsPage.clickQuest(target);
|
|
await questsPage.completeQuest();
|
|
await page.waitForTimeout(1000);
|
|
const isMiniGameVisible = await questsPage.isMiniGameModalVisible();
|
|
expect(isMiniGameVisible).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
Given('I have a dark magic quest', async ({ page }) => {
|
|
const baseUrl = global.fixtures.baseUrl;
|
|
const questsPage = global.fixtures.questsPage!;
|
|
await createQuestViaApi(page, baseUrl, {
|
|
title: 'Dark Magic Quest BDD',
|
|
description: 'A quest touched by dark magic',
|
|
status: 'Not Yet Begun',
|
|
quest_type: 'Dark Magic',
|
|
is_dark_magic: true,
|
|
});
|
|
await questsPage.goto();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
When('I create a quest with title {string} and description {string}', async ({ page }, title: string, description: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const questData: Omit<Quest, 'id'> = {
|
|
title,
|
|
description,
|
|
status: 'Not Yet Begun',
|
|
type: 'The Ring',
|
|
};
|
|
await questsPage.createQuest(questData);
|
|
testContext.addQuest(questData);
|
|
(testContext as any).lastCreatedQuestTitle = title;
|
|
});
|
|
|
|
When('I edit the quest and change the status to {string}', async ({ page }, newStatus: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const questTitle = (testContext as any).lastCreatedQuestTitle || (await questsPage.getQuestTitles())[0];
|
|
await questsPage.clickQuest(questTitle);
|
|
await page.waitForTimeout(500);
|
|
await questsPage.editQuest({ status: newStatus });
|
|
await page.waitForTimeout(500);
|
|
(testContext as any).lastEditedStatus = newStatus;
|
|
});
|
|
|
|
When('I delete the quest', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const questTitle = (testContext as any).lastCreatedQuestTitle || (await questsPage.getQuestTitles())[0];
|
|
await questsPage.clickQuest(questTitle);
|
|
await page.waitForTimeout(500);
|
|
await questsPage.deleteQuest();
|
|
await page.waitForTimeout(500);
|
|
(testContext as any).deletedQuestTitle = questTitle;
|
|
});
|
|
|
|
When('I filter quests by status {string}', async ({ page }, status: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
await questsPage.filterByStatus(status);
|
|
(testContext as any).lastFilterType = 'status';
|
|
(testContext as any).lastFilterValue = status;
|
|
});
|
|
|
|
When('I filter quests by type {string}', async ({ page }, questType: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
await questsPage.filterByType(questType);
|
|
(testContext as any).lastFilterType = 'type';
|
|
(testContext as any).lastFilterValue = questType;
|
|
});
|
|
|
|
When('I filter quests by location {string}', async ({ page }, location: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
if (typeof questsPage.filterByLocation === 'function') {
|
|
await questsPage.filterByLocation(location);
|
|
}
|
|
(testContext as any).lastFilterType = 'location';
|
|
(testContext as any).lastFilterValue = location;
|
|
});
|
|
|
|
When('I click complete on the quest', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const questTitle = (testContext as any).lastCreatedQuestTitle || (await questsPage.getQuestTitles())[0];
|
|
await questsPage.clickQuest(questTitle);
|
|
await page.waitForTimeout(500);
|
|
await questsPage.completeQuest();
|
|
await page.waitForTimeout(500);
|
|
});
|
|
|
|
When('I fail the mini-game', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
if (typeof questsPage.failMiniGame === 'function') {
|
|
await questsPage.failMiniGame();
|
|
}
|
|
});
|
|
|
|
When('I win the mini-game', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
if (typeof questsPage.winMiniGame === 'function') {
|
|
await questsPage.winMiniGame();
|
|
}
|
|
});
|
|
|
|
When('I interact with the dark magic quest', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const questTitles = await questsPage.getQuestTitles();
|
|
const darkMagicQuest = questTitles.find(t => t.includes('Dark'));
|
|
if (darkMagicQuest) {
|
|
await questsPage.clickQuest(darkMagicQuest);
|
|
await page.waitForTimeout(500);
|
|
}
|
|
});
|
|
|
|
Then('the quest should appear in the quest list', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const questTitle = (testContext as any).lastCreatedQuestTitle;
|
|
const questTitles = await questsPage.getQuestTitles();
|
|
expect(questTitles).toContain(questTitle);
|
|
});
|
|
|
|
Then('the quest should have status {string}', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Then('the quest should be updated', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const questTitle = (testContext as any).lastCreatedQuestTitle;
|
|
const questTitles = await questsPage.getQuestTitles();
|
|
expect(questTitles).toContain(questTitle);
|
|
});
|
|
|
|
Then('the new status should be displayed', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Then('the quest should no longer appear in the quest list', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const testContext = global.fixtures.testContext;
|
|
const deletedQuestTitle = (testContext as any).deletedQuestTitle;
|
|
const questTitles = await questsPage.getQuestTitles();
|
|
expect(questTitles).not.toContain(deletedQuestTitle);
|
|
});
|
|
|
|
Then('only completed quests should be displayed', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Then('only {string} type quests should be displayed', async ({ page }, _questType: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Then('only quests in {string} should be displayed', async ({ page }, _location: string) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Then('a mini-game modal should appear', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isMiniGameVisible = await questsPage.isMiniGameModalVisible();
|
|
expect(isMiniGameVisible).toBeTruthy();
|
|
});
|
|
|
|
Then('I should see one of: Trivia, Memory, Reaction, Find the Ring', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const miniGameType = await questsPage.getMiniGameType();
|
|
expect(miniGameType?.length || 0).toBeGreaterThan(0);
|
|
});
|
|
|
|
Then('I should see a {string} button', async ({ page }, buttonText: string) => {
|
|
const button = page.locator(`button:has-text("${buttonText}")`);
|
|
const isVisible = await button.isVisible({ timeout: 5000 });
|
|
expect(isVisible).toBeTruthy();
|
|
});
|
|
|
|
Then('I should be able to retry the mini-game', async ({ page }) => {
|
|
// Wait for modal to be visible
|
|
await page.locator('h2:has-text("Quest Challenge")').waitFor({ state: 'visible', timeout: 10000 });
|
|
// Wait for the retry button to appear
|
|
const retryButton = page.locator('button:has-text("Try Again")');
|
|
await retryButton.waitFor({ state: 'visible', timeout: 10000 });
|
|
expect(await retryButton.isVisible()).toBeTruthy();
|
|
});
|
|
|
|
Then('the mini-game modal should close', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
await questsPage.waitForMiniGameModalToClose();
|
|
const isMiniGameVisible = await questsPage.isMiniGameModalVisible();
|
|
expect(isMiniGameVisible).toBeFalsy();
|
|
});
|
|
|
|
Then('the quest status should change to {string}', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|
|
|
|
Then('I should receive gold reward', async ({ page }) => {
|
|
const testContext = global.fixtures.testContext;
|
|
expect(testContext).toBeDefined();
|
|
});
|
|
|
|
Then('the quest may intermittently appear or fail completion', async ({ page }) => {
|
|
const questsPage = global.fixtures.questsPage!;
|
|
const isLoaded = await questsPage.isLoaded();
|
|
expect(isLoaded).toBeTruthy();
|
|
});
|