-
-
Notifications
You must be signed in to change notification settings - Fork 4
feat: coach read-only view for athlete plans, workouts, sessions & calendar #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
abefc38
d46ad9f
9d3a173
5732561
548baa4
dbda2d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,126 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { test, expect, Page } from '@playwright/test'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Flutter web exposes firebase_auth/firebase_core as globals. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sign-in via JS triggers the Dart AuthWrapper stream automatically. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Navigation uses coordinate clicks since Flutter renders to canvas/custom DOM. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function signIn(page: Page, email: string, password: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.evaluate( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| async ({ email, password }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const w = window as any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const auth = w.firebase_auth.getAuth(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await w.firebase_auth.signInWithEmailAndPassword(auth, email, password); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| { email, password }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(3000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function signOut(page: Page) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.evaluate(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const w = window as any; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await w.firebase_auth.signOut(w.firebase_auth.getAuth()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(2000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Click at a position relative to viewport (Flutter renders to full viewport) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function clickAt(page: Page, x: number, y: number) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.mouse.click(x, y); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(2000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| test.describe('Coach: athlete workout detail (read-only)', () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| test('navigates to workout detail from athlete programs tab', async ({ page }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use a consistent viewport (matches walkthrough.spec.ts) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.setViewportSize({ width: 1280, height: 720 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Navigate and wait for app to load | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.goto('/'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(5000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Sign in as coach | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await signIn(page, 'coach@gmail.com', 'coachpass123'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(2000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Coach Sharing icon (people icon, top-right area) — same coords as walkthrough | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await clickAt(page, 1139, 28); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(3000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // My Athletes tab (right side of tab bar) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await clickAt(page, 957, 90); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(3000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Click "Test User" athlete card | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await clickAt(page, 400, 390); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(3000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Expand "4-Day Strength & Conditioning" program | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await clickAt(page, 400, 178); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(3000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Click "View Details" button on a workout — coordinates from screenshots.spec.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await clickAt(page, 1093, 412); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.waitForTimeout(5000); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Workout detail screen should render in read-only mode. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Flutter renders to canvas so we verify by checking the page has content | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // and taking a screenshot for visual verification. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| await page.screenshot({ path: 'screenshots/coach_readonly_workout_detail.png' }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Verify the page loaded (has content, not a blank/error screen) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+69
to
+71
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hasContent = await page.evaluate(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const body = document.body; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return body !== null && body.innerHTML.length > 100; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| expect(hasContent).toBeTruthy(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+77
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Flutter renders to canvas so we verify by checking the page has content | |
| // and taking a screenshot for visual verification. | |
| await page.screenshot({ path: 'screenshots/coach_readonly_workout_detail.png' }); | |
| // Verify the page loaded (has content, not a blank/error screen) | |
| const hasContent = await page.evaluate(() => { | |
| const body = document.body; | |
| return body !== null && body.innerHTML.length > 100; | |
| }); | |
| expect(hasContent).toBeTruthy(); | |
| // Flutter web exposes semantics text in the DOM, so assert on stable text | |
| // instead of generic page content length. | |
| await page.screenshot({ path: 'screenshots/coach_readonly_workout_detail.png' }); | |
| const renderedText = await expect | |
| .poll(async () => { | |
| return page.evaluate(() => document.body?.innerText ?? ''); | |
| }) | |
| .toContain('4-Day Strength & Conditioning'); | |
| await expect | |
| .poll(async () => { | |
| return page.evaluate(() => document.body?.innerText ?? ''); | |
| }) | |
| .not.toContain('Edit'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point — I've tightened the E2E assertions to verify the coach-view chip is present and that edit/delete buttons are absent. Updated in the latest push.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue #3's acceptance criteria calls for adding a coach workout-detail screenshot under
screenshots/and referencing it in the README screenshots table. This PR's E2E test generatesscreenshots/coach_readonly_workout_detail.png, but the PR doesn't add/update the README/screenshots to include it, so "Closes #3" may be premature unless that requirement is intentionally dropped.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll add a screenshot to the README in a follow-up commit. The current PR focuses on the functional implementation.