From 290b352b07ea5ae53d1d7f136221b49b2726e222 Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Sun, 13 Sep 2026 14:17:14 +0200 Subject: [PATCH] Scrape submitted text for past-due submissions, not just editable ones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Once a submission's due date passes, the legacy homework page renders the student's text read-only in a `
` that is a sibling *after* ``, which then holds only the file list. `parseHomeworkPage` searched for that div *inside* the submission section, so for every past-due submission it silently returned no submitted text while still reporting the grade and feedback — the reader would conclude the student handed in nothing. Search the whole page instead. `class="comment"` (quote right after the word) stays specific: the teacher's feedback is `class="comment ckcontent"` and does not match, and the editable-textarea branch is still tried first. Found by standing up a local instance from the deployed images and reading a real 33.40 page for a seeded past-due submission; the earlier test fixture had nested the div inside the section, which is why the gap was invisible. The fixtures now match the real DOM. Co-Authored-By: Claude Opus 4.8 --- src/core/homework-page.ts | 10 +++++++++- test/homework-page.test.ts | 25 +++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/core/homework-page.ts b/src/core/homework-page.ts index 566169e..b3f7845 100644 --- a/src/core/homework-page.ts +++ b/src/core/homework-page.ts @@ -73,9 +73,17 @@ export function parseHomeworkPage(html: string): SubmissionDetail | undefined { // The student's own text: a textarea while the submission is still editable, // a plain div once it is not. + // + // The read-only div is a *sibling after* ``, not a + // child of it — that section then holds only the file list. Scoping this + // search to the section therefore found nothing for every submission past + // its due date, silently dropping the submitted text while still reporting + // the grade. `class="comment"` (with the quote right after the word) is + // specific enough to search the whole page: the teacher's feedback is + // `class="comment ckcontent"` and so cannot match. const typed = /data-testid="submission-text"[^>]*>([\s\S]*?)<\/textarea>/.exec(html)?.[1] ?? - (submission ? /
]*>([\s\S]*?)<\/div>/.exec(submission)?.[1] : undefined); + /
]*>([\s\S]*?)<\/div>/.exec(html)?.[1]; const typedText = clean(typed); if (typedText) detail.submittedText = typedText; diff --git a/test/homework-page.test.ts b/test/homework-page.test.ts index 0327499..e14570b 100644 --- a/test/homework-page.test.ts +++ b/test/homework-page.test.ts @@ -7,11 +7,12 @@ import { parseHomeworkPage } from '../src/core/homework-page.ts'; * as actually served — including the parts that are absent for a student, which * is what makes every field optional. */ -const page = (parts: { submission?: string; feedback?: string }) => ` +const page = (parts: { submission?: string; feedback?: string; afterSubmission?: string }) => `
keine Beschreibung vorhanden
${parts.submission ?? ''}
+${parts.afterSubmission ?? ''} ${parts.feedback === undefined ? '' : `
${parts.feedback}
`} `; @@ -39,10 +40,30 @@ describe('parseHomeworkPage', () => { }); it('reads the typed answer from the read-only form once submission closed', () => { - const html = page({ submission: `

Abgegebener Text

` }); + // The read-only `
` is a sibling *after* the closed + // submission section, not a child of it — verified against a real 33.40 + // page for a past-due submission. A fixture that nested it inside the + // section (as an earlier one did) hid a bug where every past-due + // submission lost its text. See src/core/homework-page.ts. + const html = page({ + submission: `
`, + afterSubmission: `

Abgegebener Text

`, + }); assert.equal(parseHomeworkPage(html)?.submittedText, 'Abgegebener Text'); }); + it('does not confuse the submitted text with the teacher feedback comment', () => { + // Both are comment divs, distinguished only by the exact class: the + // student's is `class="comment"`, the teacher's `class="comment ckcontent"`. + const html = page({ + afterSubmission: `

Meine Abgabe

`, + feedback: `

Gut gemacht

`, + }); + const detail = parseHomeworkPage(html); + assert.equal(detail?.submittedText, 'Meine Abgabe'); + assert.equal(detail?.gradeComment, 'Gut gemacht'); + }); + it('separates submitted files from files the teacher returned', () => { const html = page({ submission: fileCard('a'.repeat(24), 'meine-abgabe.pdf'),