workspace/projects/gamilit/apps/frontend/e2e/teacher-flow.spec.ts
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00

339 lines
11 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { loginAsTeacher } from './fixtures/auth-helpers';
import { testClassrooms } from './fixtures/test-users';
import {
waitForPageLoad,
waitForToast,
getByTestId,
waitForLoadingToFinish,
waitForModal,
closeModal,
} from './helpers/page-helpers';
/**
* Teacher Flow E2E Tests
*
* Tests the complete teacher workflow:
* Login → Teacher Portal → View Submissions → Review → Grade
* Test: Receive notification of new exercise
*/
test.describe('Teacher Flow - Submission Review', () => {
test.beforeEach(async ({ page }) => {
// Login as teacher
await loginAsTeacher(page);
await waitForPageLoad(page);
});
test('should view pending submissions', async ({ page }) => {
// Verify we're on teacher dashboard
await expect(page).toHaveURL(/\/teacher\/dashboard/);
// Navigate to submissions page
const submissionsLink = page.getByRole('link', {
name: /entregas|submissions|respuestas/i,
});
if (await submissionsLink.isVisible()) {
await submissionsLink.click();
} else {
// Try navigation menu
await page.goto('/teacher/submissions');
}
await waitForPageLoad(page);
// Verify we're on submissions page
await expect(page).toHaveURL(/\/teacher.*submissions|responses/);
// Check for submissions table/list
const submissionsTable = page.locator('table, [role="table"], [data-testid="submissions-list"]');
await expect(submissionsTable.first()).toBeVisible({ timeout: 10000 });
// Filter for pending submissions
const filterDropdown = page.locator('select, [role="combobox"]').filter({
hasText: /estado|status|filtrar/i,
});
if (await filterDropdown.isVisible()) {
await filterDropdown.selectOption({ label: /pendiente|pending/i });
await waitForLoadingToFinish(page);
} else {
// Try clicking a filter button
const pendingButton = page.getByRole('button', { name: /pendiente|pending/i });
if (await pendingButton.isVisible()) {
await pendingButton.click();
await page.waitForTimeout(500);
}
}
// Verify pending submissions are shown
const pendingBadges = page.locator('text=/pendiente|pending/i');
const count = await pendingBadges.count();
expect(count).toBeGreaterThanOrEqual(0);
});
test('should review and grade a submission', async ({ page }) => {
// Navigate to submissions page
await page.goto('/teacher/submissions');
await waitForPageLoad(page);
// Find first pending submission
const firstSubmission = page
.locator('[data-testid="submission-row"], tr, .submission-card')
.filter({ hasText: /pendiente|pending/i })
.first();
// If no pending submissions found, skip test
const hasPending = await firstSubmission.isVisible({ timeout: 3000 });
if (!hasPending) {
test.skip();
return;
}
// Click to view submission details
const viewButton = firstSubmission.getByRole('button', { name: /ver|view|revisar/i });
if (await viewButton.isVisible()) {
await viewButton.click();
} else {
await firstSubmission.click();
}
await waitForPageLoad(page);
// Verify submission details page
await expect(page.locator('[data-testid="submission-details"]')).toBeVisible({
timeout: 10000,
});
// Verify student's answers are visible
const answersSection = page.locator('[data-testid="student-answers"], .student-answers');
await expect(answersSection.first()).toBeVisible({ timeout: 5000 });
// Enter grade/feedback
const gradeInput = page.locator('input[name="grade"], input[name="score"], [data-testid="grade-input"]');
if (await gradeInput.isVisible({ timeout: 3000 })) {
await gradeInput.fill('85');
}
// Enter feedback comment
const feedbackTextarea = page.locator(
'textarea[name="feedback"], textarea[name="comments"], [data-testid="feedback-textarea"]'
);
if (await feedbackTextarea.isVisible({ timeout: 3000 })) {
await feedbackTextarea.fill('Buen trabajo. Revisar la pregunta 3 para mejorar.');
}
// Submit grade
const submitGradeButton = page.getByRole('button', {
name: /enviar calificación|submit grade|guardar/i,
});
await expect(submitGradeButton).toBeVisible({ timeout: 5000 });
await submitGradeButton.click();
// Wait for submission
await waitForLoadingToFinish(page);
// Verify success message
await waitForToast(page, /calificación enviada|grade submitted|guardado/i);
// Verify status changed to graded
const statusBadge = page.locator('[data-testid="submission-status"]');
if (await statusBadge.isVisible({ timeout: 3000 })) {
await expect(statusBadge).toContainText(/calificado|graded|completado/i);
}
});
test('should receive notification for new exercise submission', async ({ page }) => {
// Check for notification bell/icon
const notificationBell = page.locator(
'[data-testid="notifications"], button[aria-label*="notification"], .notification-icon'
);
if (!(await notificationBell.isVisible({ timeout: 3000 }))) {
// Notifications might not be implemented yet
test.skip();
return;
}
// Click notification bell
await notificationBell.click();
await page.waitForTimeout(500);
// Verify notification dropdown/panel appears
const notificationPanel = page.locator(
'[data-testid="notification-panel"], .notification-dropdown, [role="menu"]'
);
await expect(notificationPanel).toBeVisible({ timeout: 5000 });
// Check for new submission notifications
const newSubmissionNotif = notificationPanel.locator(
'text=/nueva entrega|new submission|ejercicio enviado/i'
);
const hasNotifications = await newSubmissionNotif.isVisible({ timeout: 2000 });
expect(hasNotifications).toBeTruthy();
});
test('should filter submissions by student', async ({ page }) => {
// Navigate to submissions page
await page.goto('/teacher/submissions');
await waitForPageLoad(page);
// Look for student filter
const studentFilter = page.locator(
'input[placeholder*="estudiante"], input[placeholder*="student"], [data-testid="student-filter"]'
);
if (await studentFilter.isVisible({ timeout: 3000 })) {
// Enter student name
await studentFilter.fill('Test Student');
await page.waitForTimeout(1000);
// Verify filtered results
const submissionRows = page.locator('[data-testid="submission-row"], tr');
const count = await submissionRows.count();
// Should show filtered results
expect(count).toBeGreaterThanOrEqual(0);
} else {
// Filter might be implemented differently
test.skip();
}
});
test('should filter submissions by exercise', async ({ page }) => {
// Navigate to submissions page
await page.goto('/teacher/submissions');
await waitForPageLoad(page);
// Look for exercise filter dropdown
const exerciseFilter = page.locator(
'select[name="exercise"], [data-testid="exercise-filter"]'
);
if (await exerciseFilter.isVisible({ timeout: 3000 })) {
// Select first exercise option (skip "All")
const options = await exerciseFilter.locator('option').all();
if (options.length > 1) {
await exerciseFilter.selectOption({ index: 1 });
await waitForLoadingToFinish(page);
// Verify filtered results
const submissionRows = page.locator('[data-testid="submission-row"], tr');
const count = await submissionRows.count();
expect(count).toBeGreaterThanOrEqual(0);
}
} else {
test.skip();
}
});
test('should view student progress analytics', async ({ page }) => {
// Navigate to progress/analytics page
const progressLink = page.getByRole('link', { name: /progreso|progress|analíticas/i });
if (await progressLink.isVisible({ timeout: 3000 })) {
await progressLink.click();
} else {
await page.goto('/teacher/progress');
}
await waitForPageLoad(page);
// Verify we're on progress page
await expect(page).toHaveURL(/\/teacher.*progress|analytics/);
// Check for student list
const studentList = page.locator(
'[data-testid="student-list"], table, [role="table"]'
);
await expect(studentList.first()).toBeVisible({ timeout: 10000 });
// Check for progress indicators
const progressBars = page.locator('[role="progressbar"], .progress-bar');
const progressCount = await progressBars.count();
expect(progressCount).toBeGreaterThanOrEqual(0);
// Check for statistics/charts
const statsCards = page.locator('[data-testid*="stat"], .stat-card, .metric');
const statsCount = await statsCards.count();
expect(statsCount).toBeGreaterThanOrEqual(0);
});
test('should export submissions report', async ({ page }) => {
// Navigate to submissions page
await page.goto('/teacher/submissions');
await waitForPageLoad(page);
// Look for export button
const exportButton = page.getByRole('button', { name: /exportar|export|descargar/i });
if (!(await exportButton.isVisible({ timeout: 3000 }))) {
test.skip();
return;
}
// Setup download listener
const downloadPromise = page.waitForEvent('download', { timeout: 10000 });
// Click export
await exportButton.click();
// Wait for download to start
const download = await downloadPromise;
// Verify download
expect(download.suggestedFilename()).toMatch(/submissions|entregas/i);
});
test('should bulk grade multiple submissions', async ({ page }) => {
// Navigate to submissions page
await page.goto('/teacher/submissions');
await waitForPageLoad(page);
// Look for checkboxes to select multiple submissions
const checkboxes = page.locator('input[type="checkbox"][data-testid*="select"]');
const count = await checkboxes.count();
if (count < 2) {
// Need at least 2 submissions for bulk action
test.skip();
return;
}
// Select first 2 submissions
await checkboxes.nth(0).check();
await checkboxes.nth(1).check();
// Look for bulk action button
const bulkActionButton = page.getByRole('button', {
name: /acciones|actions|calificar seleccionados/i,
});
if (await bulkActionButton.isVisible({ timeout: 3000 })) {
await bulkActionButton.click();
// Wait for bulk action modal/dropdown
const bulkModal = await waitForModal(page);
// Enter bulk grade
const bulkGradeInput = bulkModal.locator('input[name="grade"]');
if (await bulkGradeInput.isVisible({ timeout: 2000 })) {
await bulkGradeInput.fill('80');
// Submit bulk grade
const submitButton = bulkModal.getByRole('button', { name: /aplicar|apply|enviar/i });
await submitButton.click();
await waitForLoadingToFinish(page);
// Verify success
await waitForToast(page, /calificaciones aplicadas|grades applied/i);
}
} else {
test.skip();
}
});
});