mirror of
https://github.com/jcreek/AutomatedAssessmentFeedbackAgent.git
synced 2026-07-12 18:43:49 +00:00
test(*): Add POM to BDD tests
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
export class HomePage {
|
||||
readonly page: Page;
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async navigateTo() {
|
||||
await this.page.goto('http://localhost:5173/');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
export class ResultsPage {
|
||||
readonly page: Page;
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
headingSelector = '[data-testid="results-heading"]';
|
||||
feedbackSelector = '[data-testid="feedback"]';
|
||||
gradeSelector = '[data-testid="feedback"] div:has(span:has-text("Grade:"))';
|
||||
strengthsSelector = '[data-testid="feedback"] div:has(span:has-text("Strengths:"))';
|
||||
areasForImprovementSelector = '[data-testid="feedback"] div:has(span:has-text("Areas for Improvement:"))';
|
||||
individualizedActivitySelector = '[data-testid="feedback"] div:has(span:has-text("Individualized Activity:"))';
|
||||
reflectionQuestionSelector = '[data-testid="feedback"] div:has(span:has-text("Reflection Question:"))';
|
||||
teacherSuggestionSelector = '[data-testid="feedback"] div:has(span:has-text("Teacher Suggestion:"))';
|
||||
spellingAndGrammarSelector = '[data-testid="feedback"] div:has(span:has-text("Spelling and Grammar:"))';
|
||||
reasoningSummarySelector = '[data-testid="feedback"] details > summary';
|
||||
reasoningTextSelector = '[data-testid="feedback"] details > div';
|
||||
backToUploadSelector = 'a[aria-label="Back to upload page"]';
|
||||
noResultsSelector = 'div[aria-live="polite"][role="status"]';
|
||||
clearHistorySelector = 'button[aria-label="Clear all assessment history"]';
|
||||
|
||||
async navigateTo() {
|
||||
await this.page.goto('http://localhost:5173/results');
|
||||
}
|
||||
|
||||
async getHeadingText() {
|
||||
return this.page.textContent(this.headingSelector);
|
||||
}
|
||||
|
||||
async waitForHeadingVisible(timeout = 60000) {
|
||||
await this.page.waitForSelector(this.headingSelector, { state: 'visible', timeout });
|
||||
}
|
||||
|
||||
async isFeedbackVisible() {
|
||||
return this.page.isVisible(this.feedbackSelector);
|
||||
}
|
||||
|
||||
async waitForFeedbackVisible(timeout = 60000) {
|
||||
await this.page.waitForSelector(this.feedbackSelector, { state: 'visible', timeout });
|
||||
}
|
||||
|
||||
async getGrade() {
|
||||
return this.page.textContent(this.gradeSelector);
|
||||
}
|
||||
|
||||
async getStrengths() {
|
||||
return this.page.textContent(this.strengthsSelector);
|
||||
}
|
||||
|
||||
async getAreasForImprovement() {
|
||||
return this.page.textContent(this.areasForImprovementSelector);
|
||||
}
|
||||
|
||||
async getIndividualizedActivity() {
|
||||
return this.page.textContent(this.individualizedActivitySelector);
|
||||
}
|
||||
|
||||
async getReflectionQuestion() {
|
||||
return this.page.textContent(this.reflectionQuestionSelector);
|
||||
}
|
||||
|
||||
async getTeacherSuggestion() {
|
||||
return this.page.textContent(this.teacherSuggestionSelector);
|
||||
}
|
||||
|
||||
async getSpellingAndGrammar() {
|
||||
return this.page.textContent(this.spellingAndGrammarSelector);
|
||||
}
|
||||
|
||||
async expandReasoning() {
|
||||
await this.page.click(this.reasoningSummarySelector);
|
||||
}
|
||||
|
||||
async getReasoning() {
|
||||
return this.page.textContent(this.reasoningTextSelector);
|
||||
}
|
||||
|
||||
async clickBackToUpload() {
|
||||
await this.page.click(this.backToUploadSelector);
|
||||
}
|
||||
|
||||
async isNoResultsMessageVisible() {
|
||||
return this.page.isVisible(this.noResultsSelector);
|
||||
}
|
||||
|
||||
async clearHistory() {
|
||||
await this.page.click(this.clearHistorySelector);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
export class UploadPage {
|
||||
readonly page: Page;
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
taskInputSelector = '[data-testid="task-input"]';
|
||||
fileInputSelector = 'input[type="file"]';
|
||||
submissionInputSelector = '[data-testid="submission-input"]';
|
||||
submitButtonSelector = '[data-testid="submit-button"]';
|
||||
|
||||
async navigateTo() {
|
||||
await this.page.goto('http://localhost:5173/upload');
|
||||
}
|
||||
|
||||
async setTaskDescription(text: string) {
|
||||
await this.page.fill(this.taskInputSelector, text);
|
||||
}
|
||||
|
||||
async uploadFile(filePath: string) {
|
||||
await this.page.setInputFiles(this.fileInputSelector, filePath);
|
||||
}
|
||||
|
||||
async setSubmissionText(text: string) {
|
||||
await this.page.fill(this.submissionInputSelector, text);
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.page.click(this.submitButtonSelector);
|
||||
}
|
||||
|
||||
async isSubmitButtonEnabled() {
|
||||
return this.page.isEnabled(this.submitButtonSelector);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber';
|
||||
import { expect } from '@playwright/test';
|
||||
import { UploadPage } from '../pages/UploadPage.ts';
|
||||
|
||||
Given('I have submitted an assignment for assessment', async function () {
|
||||
await this.page.goto('http://localhost:5173/upload');
|
||||
await this.page.fill('textarea#task', 'Write a short essay about climate change.');
|
||||
await this.page.fill('textarea#textInput', 'Climate change is a serious global issue that affects us all.');
|
||||
await this.page.click('button[type="submit"]');
|
||||
await this.uploadPage.navigateTo();
|
||||
await this.uploadPage.setTaskDescription('Write a short essay about climate change.');
|
||||
await this.uploadPage.setSubmissionText('Climate change is a serious global issue that affects us all.');
|
||||
await this.uploadPage.submit();
|
||||
});
|
||||
|
||||
When('the AI agent is processing my submission', async function () {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber';
|
||||
import { expect } from '@playwright/test';
|
||||
import { ResultsPage } from '../pages/ResultsPage.ts';
|
||||
|
||||
Given('I am viewing the assessment history', async function () {
|
||||
await this.page.goto('http://localhost:5173/results');
|
||||
await this.resultsPage.navigateTo();
|
||||
await this.page.waitForSelector('table[aria-label="Assessment history table"]');
|
||||
});
|
||||
|
||||
@@ -18,13 +19,11 @@ Given('I have previously submitted assessments', async function () {
|
||||
submission: 'Dog',
|
||||
}
|
||||
]) {
|
||||
await this.page.goto('http://localhost:5173/upload');
|
||||
await this.page.fill('[data-testid="task-input"]', assessment.task);
|
||||
await this.page.fill('[data-testid="submission-input"]', assessment.submission);
|
||||
await Promise.all([
|
||||
this.page.waitForNavigation({ url: '**/results' }),
|
||||
this.page.click('[data-testid="submit-button"]')
|
||||
]);
|
||||
await this.uploadPage.navigateTo();
|
||||
await this.uploadPage.setTaskDescription(assessment.task);
|
||||
await this.uploadPage.setSubmissionText(assessment.submission);
|
||||
await this.uploadPage.submit();
|
||||
await this.resultsPage.waitForHeadingVisible();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -66,7 +65,7 @@ Then('that assessment should be removed from the history', async function () {
|
||||
When('I clear all assessment history', async function () {
|
||||
await Promise.all([
|
||||
this.page.waitForEvent('dialog').then(dialog => dialog.accept()),
|
||||
this.page.click('button[aria-label="Clear all assessment history"]')
|
||||
this.resultsPage.clearHistory()
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
Given('I am on the homepage', async function () {
|
||||
await this.page.goto('http://localhost:5173/');
|
||||
});
|
||||
import { ResultsPage } from '../pages/ResultsPage.ts';
|
||||
|
||||
When('I fill in the assessment form and submit', async function () {
|
||||
await this.page.fill('textarea[name="assessment"]', 'Sample student submission');
|
||||
@@ -11,7 +8,7 @@ When('I fill in the assessment form and submit', async function () {
|
||||
});
|
||||
|
||||
Then('I should see my feedback displayed', async function () {
|
||||
await this.page.waitForSelector('.feedback');
|
||||
const feedback = await this.page.textContent('.feedback');
|
||||
await this.resultsPage.waitForFeedbackVisible();
|
||||
const feedback = await this.resultsPage.getGrade();
|
||||
expect(feedback).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -1,69 +1,72 @@
|
||||
import { Given, When, Then } from '@cucumber/cucumber';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
async function getFeedbackText(page: any) {
|
||||
await page.waitForSelector('[data-testid="feedback"]', { state: 'visible' });
|
||||
return page.textContent('[data-testid="feedback"]');
|
||||
}
|
||||
import { UploadPage } from '../pages/UploadPage.ts';
|
||||
import { ResultsPage } from '../pages/ResultsPage.ts';
|
||||
|
||||
Given('I am on the "Upload Student Submissions" page', async function () {
|
||||
await this.page.goto('http://localhost:5173/upload');
|
||||
await this.uploadPage.navigateTo();
|
||||
});
|
||||
|
||||
When('I enter a task description', async function () {
|
||||
await this.page.waitForSelector('[data-testid="task-input"]', { state: 'visible' });
|
||||
await this.page.fill('[data-testid="task-input"]', 'Write a short essay about climate change.');
|
||||
await this.uploadPage.setTaskDescription('Write a short essay about climate change.');
|
||||
});
|
||||
|
||||
When('I enter a student submission', async function () {
|
||||
await this.page.waitForSelector('[data-testid="submission-input"]', { state: 'visible' });
|
||||
await this.page.fill('[data-testid="submission-input"]', 'Climate change is a serious global issue that affects us all.');
|
||||
await this.uploadPage.setSubmissionText('Climate change is a serious global issue that affects us all.');
|
||||
});
|
||||
|
||||
When('I submit the assessment form', async function () {
|
||||
await this.page.waitForSelector('[data-testid="submit-button"]', { state: 'visible' });
|
||||
await this.page.click('[data-testid="submit-button"]');
|
||||
await this.uploadPage.submit();
|
||||
});
|
||||
|
||||
When('I leave the submission field empty', async function () {
|
||||
await this.page.waitForSelector('[data-testid="submission-input"]', { state: 'visible' });
|
||||
await this.page.fill('[data-testid="submission-input"]', '');
|
||||
await this.uploadPage.setSubmissionText('');
|
||||
});
|
||||
|
||||
Then('I should be redirected to the "Assessment Results" page', async function () {
|
||||
// Wait for a unique selector on the results page, increase timeout for slow AI assessment
|
||||
await this.page.waitForSelector('[data-testid="results-heading"]', { timeout: 60000 });
|
||||
const heading = await this.page.textContent('[data-testid="results-heading"]');
|
||||
// Increase timeout for slow AI assessment
|
||||
await this.page.waitForSelector(this.resultsPage.headingSelector, { timeout: 60000 });
|
||||
const heading = await this.resultsPage.getHeadingText();
|
||||
expect(heading).toMatch(/Assessment Results/);
|
||||
});
|
||||
|
||||
Then('I should see the AI-generated grade', async function () {
|
||||
const text = await getFeedbackText(this.page);
|
||||
expect(text).toMatch(/Grade:/i);
|
||||
await this.resultsPage.waitForFeedbackVisible();
|
||||
const gradeText = await this.resultsPage.getGrade();
|
||||
expect(gradeText).toMatch(/Grade:/i);
|
||||
});
|
||||
|
||||
Then('I should see strengths, areas for improvement, and individualized activity', async function () {
|
||||
const text = await getFeedbackText(this.page);
|
||||
expect(text).toMatch(/Strengths:/i);
|
||||
expect(text).toMatch(/Areas for Improvement:/i);
|
||||
expect(text).toMatch(/Individualized Activity:/i);
|
||||
await this.resultsPage.waitForFeedbackVisible();
|
||||
const strengths = await this.resultsPage.getStrengths();
|
||||
const areas = await this.resultsPage.getAreasForImprovement();
|
||||
const activity = await this.resultsPage.getIndividualizedActivity();
|
||||
expect(strengths).toBeTruthy();
|
||||
expect(areas).toBeTruthy();
|
||||
expect(activity).toBeTruthy();
|
||||
});
|
||||
|
||||
Then('I should see a reflection question and teacher suggestion', async function () {
|
||||
const text = await getFeedbackText(this.page);
|
||||
expect(text).toMatch(/Reflection Question:/i);
|
||||
expect(text).toMatch(/Teacher Suggestion:/i);
|
||||
this.resultsPage = new ResultsPage(this.page);
|
||||
await this.resultsPage.waitForFeedbackVisible();
|
||||
const reflection = await this.resultsPage.getReflectionQuestion();
|
||||
const suggestion = await this.resultsPage.getTeacherSuggestion();
|
||||
expect(reflection).toBeTruthy();
|
||||
expect(suggestion).toBeTruthy();
|
||||
});
|
||||
|
||||
Then('I should see spelling and grammar feedback', async function () {
|
||||
const text = await getFeedbackText(this.page);
|
||||
expect(text).toMatch(/Spelling and Grammar:/i);
|
||||
this.resultsPage = new ResultsPage(this.page);
|
||||
await this.resultsPage.waitForFeedbackVisible();
|
||||
const spelling = await this.resultsPage.getSpellingAndGrammar();
|
||||
expect(spelling).toBeTruthy();
|
||||
});
|
||||
|
||||
Then('I should be able to expand to view AI reasoning', async function () {
|
||||
await this.page.click('summary:has-text("Show AI Reasoning")');
|
||||
const text = await getFeedbackText(this.page);
|
||||
expect(text).toMatch(/reasoning/i);
|
||||
this.resultsPage = new ResultsPage(this.page);
|
||||
await this.resultsPage.expandReasoning();
|
||||
const reasoning = await this.resultsPage.getReasoning();
|
||||
expect(reasoning).toBeTruthy();
|
||||
});
|
||||
|
||||
Then('the submit button should be disabled', async function () {
|
||||
|
||||
@@ -6,14 +6,24 @@ setDefaultTimeout(60000); // 60 seconds
|
||||
|
||||
let browser: Browser;
|
||||
|
||||
import { HomePage } from '../pages/HomePage.ts';
|
||||
import { UploadPage } from '../pages/UploadPage.ts';
|
||||
import { ResultsPage } from '../pages/ResultsPage.ts';
|
||||
|
||||
class CustomWorld extends World {
|
||||
page!: Page;
|
||||
homePage!: HomePage;
|
||||
uploadPage!: UploadPage;
|
||||
resultsPage!: ResultsPage;
|
||||
|
||||
async openPage() {
|
||||
if (!browser) {
|
||||
browser = await chromium.launch({ headless: true });
|
||||
}
|
||||
this.page = await browser.newPage();
|
||||
this.homePage = new HomePage(this.page);
|
||||
this.uploadPage = new UploadPage(this.page);
|
||||
this.resultsPage = new ResultsPage(this.page);
|
||||
}
|
||||
|
||||
async closePage() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowImportingTsExtensions": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
|
||||
Reference in New Issue
Block a user