@falkenslab/moodle-driver 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +133 -2
- package/dist/areas/activities/build.d.ts +98 -0
- package/dist/areas/activities/build.js +246 -0
- package/dist/areas/activities/build.js.map +1 -0
- package/dist/areas/activities/content.d.ts +53 -0
- package/dist/areas/activities/content.js +113 -0
- package/dist/areas/activities/content.js.map +1 -0
- package/dist/areas/assignments/grader.d.ts +68 -0
- package/dist/areas/assignments/grader.js +169 -0
- package/dist/areas/assignments/grader.js.map +1 -0
- package/dist/areas/assignments/grading.d.ts +86 -0
- package/dist/areas/assignments/grading.js +162 -0
- package/dist/areas/assignments/grading.js.map +1 -0
- package/dist/areas/assignments/student.d.ts +57 -0
- package/dist/areas/assignments/student.js +115 -0
- package/dist/areas/assignments/student.js.map +1 -0
- package/dist/areas/calendar/calendar.d.ts +43 -0
- package/dist/areas/calendar/calendar.js +84 -0
- package/dist/areas/calendar/calendar.js.map +1 -0
- package/dist/areas/completion/completion.d.ts +50 -0
- package/dist/areas/completion/completion.js +112 -0
- package/dist/areas/completion/completion.js.map +1 -0
- package/dist/areas/courses/list.d.ts +59 -0
- package/dist/areas/courses/list.js +74 -0
- package/dist/areas/courses/list.js.map +1 -0
- package/dist/areas/courses/overview.d.ts +83 -0
- package/dist/areas/courses/overview.js +137 -0
- package/dist/areas/courses/overview.js.map +1 -0
- package/dist/areas/courses/state.d.ts +51 -0
- package/dist/areas/courses/state.js +45 -0
- package/dist/areas/courses/state.js.map +1 -0
- package/dist/areas/courses/structure.d.ts +89 -0
- package/dist/areas/courses/structure.js +104 -0
- package/dist/areas/courses/structure.js.map +1 -0
- package/dist/areas/forums/discussions.d.ts +57 -0
- package/dist/areas/forums/discussions.js +119 -0
- package/dist/areas/forums/discussions.js.map +1 -0
- package/dist/areas/forums/posts.d.ts +83 -0
- package/dist/areas/forums/posts.js +114 -0
- package/dist/areas/forums/posts.js.map +1 -0
- package/dist/areas/grades/gradebook.d.ts +104 -0
- package/dist/areas/grades/gradebook.js +250 -0
- package/dist/areas/grades/gradebook.js.map +1 -0
- package/dist/areas/grades/user-report.d.ts +26 -0
- package/dist/areas/grades/user-report.js +71 -0
- package/dist/areas/grades/user-report.js.map +1 -0
- package/dist/areas/h5p/h5p.d.ts +33 -0
- package/dist/areas/h5p/h5p.js +104 -0
- package/dist/areas/h5p/h5p.js.map +1 -0
- package/dist/areas/lessons/lessons.d.ts +60 -0
- package/dist/areas/lessons/lessons.js +145 -0
- package/dist/areas/lessons/lessons.js.map +1 -0
- package/dist/areas/messages/messages.d.ts +68 -0
- package/dist/areas/messages/messages.js +84 -0
- package/dist/areas/messages/messages.js.map +1 -0
- package/dist/areas/participants/participants.d.ts +57 -0
- package/dist/areas/participants/participants.js +135 -0
- package/dist/areas/participants/participants.js.map +1 -0
- package/dist/areas/questions/banks.d.ts +83 -0
- package/dist/areas/questions/banks.js +239 -0
- package/dist/areas/questions/banks.js.map +1 -0
- package/dist/areas/quizzes/attempts.d.ts +123 -0
- package/dist/areas/quizzes/attempts.js +300 -0
- package/dist/areas/quizzes/attempts.js.map +1 -0
- package/dist/areas/quizzes/results.d.ts +36 -0
- package/dist/areas/quizzes/results.js +93 -0
- package/dist/areas/quizzes/results.js.map +1 -0
- package/dist/areas/quizzes/structure.d.ts +42 -0
- package/dist/areas/quizzes/structure.js +100 -0
- package/dist/areas/quizzes/structure.js.map +1 -0
- package/dist/areas/site/info.d.ts +25 -0
- package/dist/areas/site/info.js +45 -0
- package/dist/areas/site/info.js.map +1 -0
- package/dist/areas/site/users.d.ts +56 -0
- package/dist/areas/site/users.js +89 -0
- package/dist/areas/site/users.js.map +1 -0
- package/dist/areas/surveys/choice.d.ts +38 -0
- package/dist/areas/surveys/choice.js +105 -0
- package/dist/areas/surveys/choice.js.map +1 -0
- package/dist/areas/surveys/surveys.d.ts +51 -0
- package/dist/areas/surveys/surveys.js +201 -0
- package/dist/areas/surveys/surveys.js.map +1 -0
- package/dist/browser/chromium.d.ts +15 -0
- package/dist/browser/chromium.js +53 -0
- package/dist/browser/chromium.js.map +1 -0
- package/dist/browser/cookies.d.ts +11 -0
- package/dist/browser/cookies.js +31 -0
- package/dist/browser/cookies.js.map +1 -0
- package/dist/browser/manager.d.ts +57 -0
- package/dist/browser/manager.js +177 -0
- package/dist/browser/manager.js.map +1 -0
- package/dist/browser.d.ts +53 -0
- package/dist/browser.js +167 -0
- package/dist/browser.js.map +1 -0
- package/dist/categories.d.ts +28 -0
- package/dist/categories.js +222 -0
- package/dist/categories.js.map +1 -0
- package/dist/chromiumInstaller.d.ts +15 -0
- package/dist/chromiumInstaller.js +52 -0
- package/dist/chromiumInstaller.js.map +1 -0
- package/dist/cli/args.d.ts +33 -0
- package/dist/cli/args.js +194 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/commands.generated.d.ts +4 -0
- package/dist/cli/commands.generated.js +2203 -0
- package/dist/cli/commands.generated.js.map +1 -0
- package/dist/cli/config.d.ts +46 -0
- package/dist/cli/config.js +107 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +19 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.d.ts +4 -0
- package/dist/cli/output.js +71 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/run.d.ts +15 -0
- package/dist/cli/run.js +194 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spec.d.ts +42 -0
- package/dist/cli/spec.js +4 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/client.d.ts +217 -0
- package/dist/client.js +179 -0
- package/dist/client.js.map +1 -0
- package/dist/core/ajax.d.ts +25 -0
- package/dist/core/ajax.js +85 -0
- package/dist/core/ajax.js.map +1 -0
- package/dist/core/editor.d.ts +18 -0
- package/dist/core/editor.js +51 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/errors.d.ts +12 -0
- package/dist/core/errors.js +10 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/files.d.ts +16 -0
- package/dist/core/files.js +58 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/forms.d.ts +45 -0
- package/dist/core/forms.js +200 -0
- package/dist/core/forms.js.map +1 -0
- package/dist/core/http.d.ts +18 -0
- package/dist/core/http.js +22 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/inPage.d.ts +8 -0
- package/dist/core/inPage.js +10 -0
- package/dist/core/inPage.js.map +1 -0
- package/dist/core/login.d.ts +40 -0
- package/dist/core/login.js +83 -0
- package/dist/core/login.js.map +1 -0
- package/dist/core/pace.d.ts +4 -0
- package/dist/core/pace.js +21 -0
- package/dist/core/pace.js.map +1 -0
- package/dist/core/session.d.ts +55 -0
- package/dist/core/session.js +157 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/site.d.ts +8 -0
- package/dist/core/site.js +36 -0
- package/dist/core/site.js.map +1 -0
- package/dist/core/text.d.ts +21 -0
- package/dist/core/text.js +113 -0
- package/dist/core/text.js.map +1 -0
- package/dist/home.d.ts +19 -0
- package/dist/home.js +54 -0
- package/dist/home.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/internals.d.ts +11 -0
- package/dist/internals.js +15 -0
- package/dist/internals.js.map +1 -0
- package/dist/moodle/activities.d.ts +94 -0
- package/dist/moodle/activities.js +240 -0
- package/dist/moodle/activities.js.map +1 -0
- package/dist/moodle/ajax.d.ts +22 -0
- package/dist/moodle/ajax.js +91 -0
- package/dist/moodle/ajax.js.map +1 -0
- package/dist/moodle/assign.d.ts +65 -0
- package/dist/moodle/assign.js +162 -0
- package/dist/moodle/assign.js.map +1 -0
- package/dist/moodle/calendar.d.ts +41 -0
- package/dist/moodle/calendar.js +80 -0
- package/dist/moodle/calendar.js.map +1 -0
- package/dist/moodle/choice.d.ts +33 -0
- package/dist/moodle/choice.js +100 -0
- package/dist/moodle/choice.js.map +1 -0
- package/dist/moodle/completion.d.ts +48 -0
- package/dist/moodle/completion.js +108 -0
- package/dist/moodle/completion.js.map +1 -0
- package/dist/moodle/content.d.ts +43 -0
- package/dist/moodle/content.js +105 -0
- package/dist/moodle/content.js.map +1 -0
- package/dist/moodle/course.d.ts +51 -0
- package/dist/moodle/course.js +45 -0
- package/dist/moodle/course.js.map +1 -0
- package/dist/moodle/courses.d.ts +57 -0
- package/dist/moodle/courses.js +70 -0
- package/dist/moodle/courses.js.map +1 -0
- package/dist/moodle/editor.d.ts +18 -0
- package/dist/moodle/editor.js +50 -0
- package/dist/moodle/editor.js.map +1 -0
- package/dist/moodle/files.d.ts +16 -0
- package/dist/moodle/files.js +57 -0
- package/dist/moodle/files.js.map +1 -0
- package/dist/moodle/forms.d.ts +44 -0
- package/dist/moodle/forms.js +199 -0
- package/dist/moodle/forms.js.map +1 -0
- package/dist/moodle/forum.d.ts +55 -0
- package/dist/moodle/forum.js +115 -0
- package/dist/moodle/forum.js.map +1 -0
- package/dist/moodle/forumPosts.d.ts +83 -0
- package/dist/moodle/forumPosts.js +113 -0
- package/dist/moodle/forumPosts.js.map +1 -0
- package/dist/moodle/gradebook.d.ts +102 -0
- package/dist/moodle/gradebook.js +246 -0
- package/dist/moodle/gradebook.js.map +1 -0
- package/dist/moodle/grades.d.ts +24 -0
- package/dist/moodle/grades.js +67 -0
- package/dist/moodle/grades.js.map +1 -0
- package/dist/moodle/grading.d.ts +92 -0
- package/dist/moodle/grading.js +161 -0
- package/dist/moodle/grading.js.map +1 -0
- package/dist/moodle/h5p.d.ts +31 -0
- package/dist/moodle/h5p.js +100 -0
- package/dist/moodle/h5p.js.map +1 -0
- package/dist/moodle/inPage.d.ts +8 -0
- package/dist/moodle/inPage.js +10 -0
- package/dist/moodle/inPage.js.map +1 -0
- package/dist/moodle/lesson.d.ts +57 -0
- package/dist/moodle/lesson.js +140 -0
- package/dist/moodle/lesson.js.map +1 -0
- package/dist/moodle/login.d.ts +37 -0
- package/dist/moodle/login.js +77 -0
- package/dist/moodle/login.js.map +1 -0
- package/dist/moodle/messages.d.ts +66 -0
- package/dist/moodle/messages.js +80 -0
- package/dist/moodle/messages.js.map +1 -0
- package/dist/moodle/overview.d.ts +83 -0
- package/dist/moodle/overview.js +137 -0
- package/dist/moodle/overview.js.map +1 -0
- package/dist/moodle/pace.d.ts +4 -0
- package/dist/moodle/pace.js +19 -0
- package/dist/moodle/pace.js.map +1 -0
- package/dist/moodle/participants.d.ts +55 -0
- package/dist/moodle/participants.js +131 -0
- package/dist/moodle/participants.js.map +1 -0
- package/dist/moodle/questionBank.d.ts +81 -0
- package/dist/moodle/questionBank.js +235 -0
- package/dist/moodle/questionBank.js.map +1 -0
- package/dist/moodle/quiz.d.ts +117 -0
- package/dist/moodle/quiz.js +291 -0
- package/dist/moodle/quiz.js.map +1 -0
- package/dist/moodle/quizResults.d.ts +36 -0
- package/dist/moodle/quizResults.js +93 -0
- package/dist/moodle/quizResults.js.map +1 -0
- package/dist/moodle/quizStructure.d.ts +42 -0
- package/dist/moodle/quizStructure.js +100 -0
- package/dist/moodle/quizStructure.js.map +1 -0
- package/dist/moodle/session.d.ts +39 -0
- package/dist/moodle/session.js +99 -0
- package/dist/moodle/session.js.map +1 -0
- package/dist/moodle/site.d.ts +8 -0
- package/dist/moodle/site.js +36 -0
- package/dist/moodle/site.js.map +1 -0
- package/dist/moodle/siteInfo.d.ts +25 -0
- package/dist/moodle/siteInfo.js +45 -0
- package/dist/moodle/siteInfo.js.map +1 -0
- package/dist/moodle/structure.d.ts +94 -0
- package/dist/moodle/structure.js +103 -0
- package/dist/moodle/structure.js.map +1 -0
- package/dist/moodle/submission.d.ts +48 -0
- package/dist/moodle/submission.js +109 -0
- package/dist/moodle/submission.js.map +1 -0
- package/dist/moodle/survey.d.ts +47 -0
- package/dist/moodle/survey.js +197 -0
- package/dist/moodle/survey.js.map +1 -0
- package/dist/moodle/text.d.ts +21 -0
- package/dist/moodle/text.js +113 -0
- package/dist/moodle/text.js.map +1 -0
- package/dist/moodle/user.d.ts +54 -0
- package/dist/moodle/user.js +78 -0
- package/dist/moodle/user.js.map +1 -0
- package/dist/options.d.ts +30 -0
- package/dist/options.js +35 -0
- package/dist/options.js.map +1 -0
- package/dist/sessionCookies.d.ts +11 -0
- package/dist/sessionCookies.js +31 -0
- package/dist/sessionCookies.js.map +1 -0
- package/dist/testing/fakeMoodle.d.ts +55 -0
- package/dist/testing/fakeMoodle.js +103 -0
- package/dist/testing/fakeMoodle.js.map +1 -0
- package/dist/testing/gradingPanel.d.ts +6 -0
- package/dist/testing/gradingPanel.js +22 -0
- package/dist/testing/gradingPanel.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +18 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +77 -4
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { GradingError, NotAnAssignmentError, assignmentIds, fillGradingForm, gradingPanelHtml, listParticipants, panelGrade, participantDates, readSubmissionContent, submitGradingForm } from "./assign.js";
|
|
2
|
+
import { isoDate } from "./courses.js";
|
|
3
|
+
import { dateSelectorDate, dateSelectorValues, openForm, setFields, submitForm } from "./forms.js";
|
|
4
|
+
import { NotLoggedInError } from "./session.js";
|
|
5
|
+
import { inPage } from "./inPage.js";
|
|
6
|
+
/** The grader's feedback comments field, when the assignment has them on. */
|
|
7
|
+
const FEEDBACK_FIELD = "assignfeedbackcomments_editor[text]";
|
|
8
|
+
/** An extension's date, as its form holds it (no service open to AJAX gives it). */
|
|
9
|
+
async function extensionDate(context, siteUrl, cmid, userId) {
|
|
10
|
+
const page = await openForm(context, siteUrl, `mod/assign/view.php?id=${cmid}&action=grantextension&userid=${userId}`).catch((error) => {
|
|
11
|
+
if (error instanceof NotLoggedInError)
|
|
12
|
+
throw error;
|
|
13
|
+
return undefined;
|
|
14
|
+
});
|
|
15
|
+
if (!page)
|
|
16
|
+
return undefined;
|
|
17
|
+
try {
|
|
18
|
+
return (await dateSelectorDate(page, "extensionduedate"))?.toISOString();
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
await page.close().catch(() => undefined);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Every participant's submission to an assignment, with its content unless `includeContent` is false. */
|
|
25
|
+
export async function assignmentSubmissions(context, siteUrl, cmid, { includeContent = true } = {}) {
|
|
26
|
+
const ids = await assignmentIds(context, siteUrl, cmid);
|
|
27
|
+
const participants = await listParticipants(context, siteUrl, ids.instanceId);
|
|
28
|
+
// A blank tab parses the panels' HTML: nothing of Moodle's loads there.
|
|
29
|
+
const parser = includeContent ? await context.newPage() : undefined;
|
|
30
|
+
const submissions = [];
|
|
31
|
+
try {
|
|
32
|
+
// One participant at a time: the calls share the kept service tab (ADR-004).
|
|
33
|
+
for (const participant of participants) {
|
|
34
|
+
const dates = await participantDates(context, siteUrl, ids.instanceId, participant.id);
|
|
35
|
+
const status = participant.submissionstatus || "none";
|
|
36
|
+
const withContent = parser && (status === "submitted" || status === "draft");
|
|
37
|
+
submissions.push({
|
|
38
|
+
userId: participant.id,
|
|
39
|
+
fullName: participant.fullname,
|
|
40
|
+
status,
|
|
41
|
+
submitted: participant.submitted,
|
|
42
|
+
needsGrading: participant.requiregrading,
|
|
43
|
+
extensionGranted: participant.grantedextension,
|
|
44
|
+
extensionDueDate: participant.grantedextension ? await extensionDate(context, siteUrl, cmid, participant.id) : undefined,
|
|
45
|
+
allowSubmissionsFrom: isoDate(dates.allowsubmissionsfromdate),
|
|
46
|
+
dueDate: isoDate(dates.duedate),
|
|
47
|
+
cutoffDate: isoDate(dates.cutoffdate),
|
|
48
|
+
content: withContent ? await readSubmissionContent(parser, await gradingPanelHtml(context, siteUrl, ids.contextId, participant.id)) : undefined,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
await parser?.close().catch(() => undefined);
|
|
54
|
+
}
|
|
55
|
+
return { assignment: { id: cmid, instanceId: ids.instanceId }, submissions };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Grades a participant as the grader does: a grade (simple grading), a rubric's level per criterion
|
|
59
|
+
* or a marking guide's score per criterion (by their ids, from `rubric()`), remarks per criterion,
|
|
60
|
+
* feedback comments (HTML), whether to notify, and any other grading field by name. Returns the
|
|
61
|
+
* grade saved and the gradebook's grade, the total Moodle computes for a rubric or a guide.
|
|
62
|
+
*/
|
|
63
|
+
export async function gradeSubmission(context, siteUrl, cmid, userId, { grade, rubric, guide, remarks, feedback, notify, fields, }) {
|
|
64
|
+
const ids = await assignmentIds(context, siteUrl, cmid);
|
|
65
|
+
const values = { ...fields };
|
|
66
|
+
if (grade !== undefined)
|
|
67
|
+
values.grade = grade;
|
|
68
|
+
for (const [criterion, level] of Object.entries(rubric ?? {}))
|
|
69
|
+
values[`advancedgrading[criteria][${criterion}][levelid]`] = String(level);
|
|
70
|
+
for (const [criterion, score] of Object.entries(guide ?? {}))
|
|
71
|
+
values[`advancedgrading[criteria][${criterion}][score]`] = String(score);
|
|
72
|
+
for (const [criterion, remark] of Object.entries(remarks ?? {}))
|
|
73
|
+
values[`advancedgrading[criteria][${criterion}][remark]`] = remark;
|
|
74
|
+
if (feedback !== undefined)
|
|
75
|
+
values[FEEDBACK_FIELD] = feedback;
|
|
76
|
+
if (notify !== undefined)
|
|
77
|
+
values.sendstudentnotifications = notify ? 1 : 0;
|
|
78
|
+
// A blank tab holds the panel's form: nothing of the grader's page needs to load.
|
|
79
|
+
const page = await context.newPage();
|
|
80
|
+
try {
|
|
81
|
+
const serialized = await fillGradingForm(page, await gradingPanelHtml(context, siteUrl, ids.contextId, userId), values).catch((error) => {
|
|
82
|
+
if (feedback !== undefined && error.message.includes(FEEDBACK_FIELD)) {
|
|
83
|
+
throw new Error("This assignment has no feedback comments: grade it without feedback, or turn feedback comments on in its settings.");
|
|
84
|
+
}
|
|
85
|
+
throw error;
|
|
86
|
+
});
|
|
87
|
+
await submitGradingForm(context, siteUrl, ids.instanceId, userId, serialized);
|
|
88
|
+
const saved = await panelGrade(page, await gradingPanelHtml(context, siteUrl, ids.contextId, userId));
|
|
89
|
+
return { changed: `Graded user ${userId} in assignment ${cmid}${feedback !== undefined ? ", with feedback" : ""}.`, ...saved };
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
await page.close().catch(() => undefined);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** The grading method and its criteria, read from a grading panel's form by field names and blocks. */
|
|
96
|
+
export async function readGradingDefinition(page, panelHtml) {
|
|
97
|
+
await page.setContent(panelHtml);
|
|
98
|
+
// Through inPage: it has a helper function (see the conventions on page.evaluate).
|
|
99
|
+
return inPage(page, () => {
|
|
100
|
+
const doc = globalThis.document;
|
|
101
|
+
const text = (el) => (el?.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
102
|
+
const method = doc.querySelector(".gradingform_rubric") ? "rubric" : doc.querySelector(".gradingform_guide") ? "guide" : "simple";
|
|
103
|
+
const criteria = Array.from(doc.querySelectorAll('.gradingform_rubric tr.criterion, .gradingform_guide tr.criterion')).flatMap((row) => {
|
|
104
|
+
const id = Number(/-criteria-(\d+)$/.exec(row.id)?.[1]);
|
|
105
|
+
if (!id)
|
|
106
|
+
return [];
|
|
107
|
+
if (method === "rubric") {
|
|
108
|
+
const levels = Array.from(row.querySelectorAll("td.level")).map((level) => ({
|
|
109
|
+
id: Number(level.querySelector('input[type="radio"]')?.value),
|
|
110
|
+
definition: text(level.querySelector(".definition")),
|
|
111
|
+
score: Number(text(level.querySelector(".scorevalue"))),
|
|
112
|
+
}));
|
|
113
|
+
return [{ id, description: text(row.querySelector("td.description")), levels }];
|
|
114
|
+
}
|
|
115
|
+
const max = Number(text(row.querySelector(".criteriondescriptionscore")));
|
|
116
|
+
const details = text(row.querySelector(".criteriondescription"));
|
|
117
|
+
const markers = text(row.querySelector(".criteriondescriptionmarkers"));
|
|
118
|
+
return [{ id, description: text(row.querySelector(".criterionshortname")), ...(details ? { details } : {}), ...(markers ? { markers } : {}), ...(max ? { maxScore: max } : {}) }];
|
|
119
|
+
});
|
|
120
|
+
return { method, criteria };
|
|
121
|
+
}, null);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* An assignment's grading method and, for a rubric or a marking guide, its criteria with the ids
|
|
125
|
+
* `gradeSubmission` takes: read from the grader's panel for its first participant.
|
|
126
|
+
*/
|
|
127
|
+
export async function rubric(context, siteUrl, cmid) {
|
|
128
|
+
const ids = await assignmentIds(context, siteUrl, cmid);
|
|
129
|
+
const [first] = await listParticipants(context, siteUrl, ids.instanceId);
|
|
130
|
+
if (!first)
|
|
131
|
+
throw new GradingError(`Assignment ${cmid} has no participants: its grading form can't be read without one.`);
|
|
132
|
+
const page = await context.newPage();
|
|
133
|
+
try {
|
|
134
|
+
return { assignment: { id: cmid }, ...(await readGradingDefinition(page, await gradingPanelHtml(context, siteUrl, ids.contextId, first.id))) };
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await page.close().catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Grants participants an extension until a date, through the assignment's extension form (several at
|
|
142
|
+
* once as its bulk action does). Moodle refuses a date before the assignment's due date.
|
|
143
|
+
*/
|
|
144
|
+
export async function grantExtension(context, siteUrl, cmid, userIds, until) {
|
|
145
|
+
if (userIds.length === 0)
|
|
146
|
+
throw new GradingError("Give the participants to grant the extension to.");
|
|
147
|
+
const page = await openForm(context, siteUrl, `mod/assign/view.php?id=${cmid}&action=grantextension&userid=${userIds[0]}`).catch((error) => {
|
|
148
|
+
if (error instanceof NotLoggedInError)
|
|
149
|
+
throw error;
|
|
150
|
+
throw new NotAnAssignmentError(`You can't grant extensions in activity ${cmid}: it isn't an assignment, or you aren't allowed.`);
|
|
151
|
+
});
|
|
152
|
+
try {
|
|
153
|
+
await setFields(page, { selectedusers: userIds.join(","), "extensionduedate[enabled]": true, ...(await dateSelectorValues(page, "extensionduedate", until)) });
|
|
154
|
+
await submitForm(page, "submitbutton");
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
await page.close().catch(() => undefined);
|
|
158
|
+
}
|
|
159
|
+
return { changed: `Extension until ${until.toISOString()} granted to user${userIds.length > 1 ? "s" : ""} ${userIds.join(", ")} in assignment ${cmid}.` };
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=grading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grading.js","sourceRoot":"","sources":["../../src/moodle/grading.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
/** The activity isn't an H5P activity the person can open. */
|
|
3
|
+
export declare class NotAnH5PActivityError extends Error {
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export interface H5PAttempt {
|
|
7
|
+
/** Moodle's id for the attempt (its report's), and its number for the person. */
|
|
8
|
+
attemptId?: number;
|
|
9
|
+
number: number;
|
|
10
|
+
score?: number;
|
|
11
|
+
maxScore?: number;
|
|
12
|
+
completed?: boolean;
|
|
13
|
+
passed?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface H5PActivity {
|
|
16
|
+
id: number;
|
|
17
|
+
name: string;
|
|
18
|
+
/** The H5P library ("H5P.MultiChoice") and its version, from the content's player. */
|
|
19
|
+
contentType?: {
|
|
20
|
+
library: string;
|
|
21
|
+
version: string;
|
|
22
|
+
};
|
|
23
|
+
/** Whether Moodle records attempts and scores (the activity's tracking setting). */
|
|
24
|
+
tracked: boolean;
|
|
25
|
+
/** The person's own attempts, oldest first: a teacher's report lists participants instead, so none. */
|
|
26
|
+
attempts: H5PAttempt[];
|
|
27
|
+
}
|
|
28
|
+
/** The rows of the person's "all attempts" table: positions fixed by Moodle's template, icons by class. */
|
|
29
|
+
export declare function readH5PAttempts(page: Page): Promise<H5PAttempt[]>;
|
|
30
|
+
/** An H5P activity: its content type, whether attempts are tracked, and the person's attempts with their scores. */
|
|
31
|
+
export declare function h5pActivity(context: BrowserContext, siteUrl: string, cmid: number): Promise<H5PActivity>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { inPage } from "./inPage.js";
|
|
2
|
+
import { openSitePage } from "./session.js";
|
|
3
|
+
import { decodeEntities } from "./text.js";
|
|
4
|
+
/** The activity isn't an H5P activity the person can open. */
|
|
5
|
+
export class NotAnH5PActivityError extends Error {
|
|
6
|
+
name = "NotAnH5PActivityError";
|
|
7
|
+
}
|
|
8
|
+
/** The rows of the person's "all attempts" table: positions fixed by Moodle's template, icons by class. */
|
|
9
|
+
export function readH5PAttempts(page) {
|
|
10
|
+
// Through inPage: it has a helper function (see the conventions on page.evaluate).
|
|
11
|
+
return inPage(page, () => {
|
|
12
|
+
const doc = globalThis.document;
|
|
13
|
+
// The page shows the scored attempt's table first and every attempt's last.
|
|
14
|
+
const tables = Array.from(doc.querySelectorAll("#region-main table")).filter((table) => table.querySelector('a[href*="attemptid="]'));
|
|
15
|
+
const all = tables[tables.length - 1];
|
|
16
|
+
if (!all)
|
|
17
|
+
return [];
|
|
18
|
+
// An attempt row starts with its number; a teacher's participants table has names there.
|
|
19
|
+
const rows = Array.from(all.querySelectorAll("tbody tr")).filter((row) => /^\d+$/.test((row.querySelector("th")?.textContent ?? "").trim()) && row.querySelectorAll("td").length >= 6);
|
|
20
|
+
return rows.map((row) => {
|
|
21
|
+
const cells = Array.from(row.querySelectorAll("td"));
|
|
22
|
+
// The score cell starts with the raw score; a hidden " / max" follows for small screens.
|
|
23
|
+
const raw = Array.from(cells[1]?.childNodes ?? [])
|
|
24
|
+
.filter((node) => node.nodeType === 3)
|
|
25
|
+
.map((node) => node.textContent ?? "")
|
|
26
|
+
.join("")
|
|
27
|
+
.trim();
|
|
28
|
+
const max = (cells[2]?.textContent ?? "").trim();
|
|
29
|
+
const completion = cells[4]?.querySelector("img, i");
|
|
30
|
+
const success = cells[5]?.querySelector("i, img");
|
|
31
|
+
const mark = (icon) => {
|
|
32
|
+
if (!icon)
|
|
33
|
+
return undefined;
|
|
34
|
+
const look = `${icon.className} ${icon.getAttribute("src") ?? ""}`;
|
|
35
|
+
return /check|-y\b|completion-[a-z]+-y/.test(look) ? true : /circle|-n\b/.test(look) ? false : undefined;
|
|
36
|
+
};
|
|
37
|
+
const link = row.querySelector('a[href*="attemptid="]')?.getAttribute("href") ?? "";
|
|
38
|
+
return {
|
|
39
|
+
...(/attemptid=(\d+)/.test(link) ? { attemptId: Number(/attemptid=(\d+)/.exec(link)[1]) } : {}),
|
|
40
|
+
number: Number((row.querySelector("th")?.textContent ?? "").trim()),
|
|
41
|
+
...(raw !== "" && !Number.isNaN(Number(raw)) ? { score: Number(raw) } : {}),
|
|
42
|
+
...(max !== "" && !Number.isNaN(Number(max)) ? { maxScore: Number(max) } : {}),
|
|
43
|
+
...(mark(completion) !== undefined ? { completed: mark(completion) } : {}),
|
|
44
|
+
...(mark(success) !== undefined ? { passed: mark(success) } : {}),
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}, null);
|
|
48
|
+
}
|
|
49
|
+
/** An H5P activity: its content type, whether attempts are tracked, and the person's attempts with their scores. */
|
|
50
|
+
export async function h5pActivity(context, siteUrl, cmid) {
|
|
51
|
+
const page = await openSitePage(context, siteUrl, `mod/h5pactivity/view.php?id=${cmid}`);
|
|
52
|
+
let facts;
|
|
53
|
+
try {
|
|
54
|
+
if ((await page.locator("body#page-mod-h5pactivity-view").count()) === 0) {
|
|
55
|
+
throw new NotAnH5PActivityError(`Activity ${cmid} isn't an H5P activity you can open. Take the id of an H5P activity (module "h5pactivity") from the course structure.`);
|
|
56
|
+
}
|
|
57
|
+
facts = await page.evaluate(() => {
|
|
58
|
+
const doc = globalThis.document;
|
|
59
|
+
return {
|
|
60
|
+
name: doc.querySelector("[data-activityname]")?.getAttribute("data-activityname") ?? "",
|
|
61
|
+
player: doc.querySelector("iframe.h5p-player")?.getAttribute("src") ?? undefined,
|
|
62
|
+
// The attempts report is linked only where Moodle tracks attempts.
|
|
63
|
+
tracked: doc.querySelector('a[href*="/mod/h5pactivity/report.php"]') !== null,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await page.close().catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
const activity = { id: cmid, name: decodeEntities(facts.name), tracked: facts.tracked, attempts: [] };
|
|
71
|
+
if (facts.player) {
|
|
72
|
+
// The player page names the content's main library in its H5P settings, before any script runs.
|
|
73
|
+
const player = await context.newPage();
|
|
74
|
+
let html = "";
|
|
75
|
+
try {
|
|
76
|
+
await player.goto(new URL(facts.player, siteUrl).href, { waitUntil: "domcontentloaded" });
|
|
77
|
+
html = await player.content();
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// No player to read: the content type stays unknown.
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await player.close().catch(() => undefined);
|
|
84
|
+
}
|
|
85
|
+
const library = /"library":"([^"\s]+) (\d+\.\d+)"/.exec(html);
|
|
86
|
+
if (library)
|
|
87
|
+
activity.contentType = { library: library[1], version: library[2] };
|
|
88
|
+
}
|
|
89
|
+
if (facts.tracked) {
|
|
90
|
+
const report = await openSitePage(context, siteUrl, `mod/h5pactivity/report.php?id=${cmid}`);
|
|
91
|
+
try {
|
|
92
|
+
activity.attempts = (await readH5PAttempts(report)).sort((a, b) => a.number - b.number);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
await report.close().catch(() => undefined);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return activity;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=h5p.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"h5p.js","sourceRoot":"","sources":["../../src/moodle/h5p.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Page } from "playwright-core";
|
|
2
|
+
/**
|
|
3
|
+
* Runs `fn(arg)` in the page, like `page.evaluate`, but with a `__name` helper defined first: tsx
|
|
4
|
+
* (the tests) compiles with esbuild's keepNames, which wraps named inner functions in `__name(...)`
|
|
5
|
+
* calls the page doesn't have (confirmed empirically). The bundle doesn't, and the helper is unused
|
|
6
|
+
* there. `arg` must be JSON.
|
|
7
|
+
*/
|
|
8
|
+
export declare function inPage<A, R>(page: Page, fn: (arg: A) => R | Promise<R>, arg: A): Promise<R>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `fn(arg)` in the page, like `page.evaluate`, but with a `__name` helper defined first: tsx
|
|
3
|
+
* (the tests) compiles with esbuild's keepNames, which wraps named inner functions in `__name(...)`
|
|
4
|
+
* calls the page doesn't have (confirmed empirically). The bundle doesn't, and the helper is unused
|
|
5
|
+
* there. `arg` must be JSON.
|
|
6
|
+
*/
|
|
7
|
+
export function inPage(page, fn, arg) {
|
|
8
|
+
return page.evaluate(`(() => { const __name = (f) => f; return (${fn.toString()})(${JSON.stringify(arg)}); })()`);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=inPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inPage.js","sourceRoot":"","sources":["../../src/moodle/inPage.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAO,IAAU,EAAE,EAA8B,EAAE,GAAM;IAC7E,OAAO,IAAI,CAAC,QAAQ,CAAC,6CAA6C,EAAE,CAAC,QAAQ,EAAE,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAe,CAAC;AAClI,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import type { Trail } from "../options.js";
|
|
3
|
+
/** The lesson isn't there, or the page can't take that answer. */
|
|
4
|
+
export declare class LessonError extends Error {
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
/** One input of a question page, by its form name. */
|
|
8
|
+
export interface LessonField {
|
|
9
|
+
name: string;
|
|
10
|
+
kind: "choice" | "checkbox" | "text" | "select";
|
|
11
|
+
label?: string;
|
|
12
|
+
options?: {
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
17
|
+
export interface LessonPage {
|
|
18
|
+
/** What the page is: content with buttons, a question, the feedback after an answer, or the lesson's end. */
|
|
19
|
+
kind: "content" | "question" | "feedback" | "end";
|
|
20
|
+
/** The page's id; for feedback, the page it goes on to (-9 is the end). */
|
|
21
|
+
pageId?: number;
|
|
22
|
+
title?: string;
|
|
23
|
+
/** The page's text as shown: its content, the question, Moodle's messages, the score at the end. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** A content page's buttons, by label, and the page each jumps to (Moodle's jump codes: -1 next, -9 end…). */
|
|
26
|
+
buttons?: {
|
|
27
|
+
label: string;
|
|
28
|
+
jumpTo: number;
|
|
29
|
+
}[];
|
|
30
|
+
/** A question page's inputs. */
|
|
31
|
+
fields?: LessonField[];
|
|
32
|
+
/** After an answer: whether Moodle took it as right, and the teacher's response to it. */
|
|
33
|
+
feedback?: {
|
|
34
|
+
correct?: boolean;
|
|
35
|
+
response?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** The lesson page on screen, read by its forms and blocks. */
|
|
39
|
+
export declare function readLessonPage(page: Page): Promise<LessonPage>;
|
|
40
|
+
/** The lesson page the person is on (starting the lesson the first time). */
|
|
41
|
+
export declare function lessonPage(context: BrowserContext, siteUrl: string, cmid: number): Promise<LessonPage>;
|
|
42
|
+
/**
|
|
43
|
+
* Answers the page the person is on: a content page's button (its label or position), or a
|
|
44
|
+
* question's fields by name. Returns Moodle's feedback when it gives one, and the page that follows,
|
|
45
|
+
* going on past the feedback as its "Continue" does; reaching the end ends the attempt. With the
|
|
46
|
+
* realistic trail it first takes the time a reader needs for the page (ADR-005).
|
|
47
|
+
*/
|
|
48
|
+
export declare function answerLessonPage(context: BrowserContext, siteUrl: string, cmid: number, answer: {
|
|
49
|
+
button?: string | number;
|
|
50
|
+
responses?: Record<string, string>;
|
|
51
|
+
}, { trail, onProgress }?: {
|
|
52
|
+
trail?: Trail;
|
|
53
|
+
onProgress?: (elapsed: number, total: number) => void;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
feedback?: LessonPage;
|
|
56
|
+
page: LessonPage;
|
|
57
|
+
}>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { setFields } from "./forms.js";
|
|
2
|
+
import { inPage } from "./inPage.js";
|
|
3
|
+
import { pause, readingSeconds } from "./pace.js";
|
|
4
|
+
import { openSitePage } from "./session.js";
|
|
5
|
+
/** The lesson isn't there, or the page can't take that answer. */
|
|
6
|
+
export class LessonError extends Error {
|
|
7
|
+
name = "LessonError";
|
|
8
|
+
}
|
|
9
|
+
/** The lesson page on screen, read by its forms and blocks. */
|
|
10
|
+
export function readLessonPage(page) {
|
|
11
|
+
return inPage(page, () => {
|
|
12
|
+
const doc = globalThis.document;
|
|
13
|
+
const main = doc.querySelector('#region-main [role="main"]') ?? doc.querySelector("#region-main");
|
|
14
|
+
const text = (el) => (el?.innerText ?? "").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim();
|
|
15
|
+
const title = text(main?.querySelector("h2")) || undefined;
|
|
16
|
+
const question = main?.querySelector('form.mform[action*="/mod/lesson/continue.php"]');
|
|
17
|
+
const pageIdOf = (form) => Number(form?.querySelector('input[name="pageid"]')?.getAttribute("value")) || undefined;
|
|
18
|
+
if (question) {
|
|
19
|
+
const fields = [];
|
|
20
|
+
for (const input of Array.from(question.querySelectorAll("input:not([type=hidden]), select, textarea"))) {
|
|
21
|
+
if (!input.name || input.type === "submit")
|
|
22
|
+
continue;
|
|
23
|
+
const label = text(input.closest("label")) || undefined;
|
|
24
|
+
if (input.type === "radio") {
|
|
25
|
+
let field = fields.find((f) => f.name === input.name);
|
|
26
|
+
if (!field) {
|
|
27
|
+
field = { name: input.name, kind: "choice", options: [] };
|
|
28
|
+
fields.push(field);
|
|
29
|
+
}
|
|
30
|
+
field.options.push({ value: input.value, label: label ?? input.value });
|
|
31
|
+
}
|
|
32
|
+
else if (input.type === "checkbox")
|
|
33
|
+
fields.push({ name: input.name, kind: "checkbox", ...(label ? { label } : {}) });
|
|
34
|
+
else if (input.tagName === "SELECT") {
|
|
35
|
+
fields.push({ name: input.name, kind: "select", ...(label ? { label } : {}), options: Array.from(input.options).map((o) => ({ value: o.value, label: o.text.trim() })) });
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
fields.push({ name: input.name, kind: "text", ...(label ? { label } : {}) });
|
|
39
|
+
}
|
|
40
|
+
return { kind: "question", pageId: pageIdOf(question), ...(title ? { title } : {}), text: text(question.querySelector(".contents")), fields };
|
|
41
|
+
}
|
|
42
|
+
const buttons = Array.from(main?.querySelectorAll('.branchbuttoncontainer form[action*="/mod/lesson/continue.php"]') ?? []);
|
|
43
|
+
if (buttons.length > 0) {
|
|
44
|
+
return {
|
|
45
|
+
kind: "content",
|
|
46
|
+
pageId: pageIdOf(buttons[0]),
|
|
47
|
+
...(title ? { title } : {}),
|
|
48
|
+
text: text(main?.querySelector(".contents")),
|
|
49
|
+
buttons: buttons.map((form) => ({ label: text(form.querySelector("button, [type=submit]")), jumpTo: Number(form.querySelector('input[name="jumpto"]')?.getAttribute("value")) })),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const next = main?.querySelector('form[action*="/mod/lesson/view.php"]');
|
|
53
|
+
if (next) {
|
|
54
|
+
const response = main?.querySelector(".response");
|
|
55
|
+
const correct = response ? (/\bcorrect\b/.test(response.className) ? true : /\bincorrect\b/.test(response.className) ? false : undefined) : undefined;
|
|
56
|
+
return {
|
|
57
|
+
kind: "feedback",
|
|
58
|
+
pageId: pageIdOf(next),
|
|
59
|
+
text: text(main).replace(text(next), "").trim(),
|
|
60
|
+
feedback: { ...(correct !== undefined ? { correct } : {}), ...(response ? { response: text(response).replace(/^[^:\n]*:\s*/, "") } : {}) },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { kind: "end", ...(title ? { title } : {}), text: text(main) };
|
|
64
|
+
}, null);
|
|
65
|
+
}
|
|
66
|
+
/** Opens the lesson where the person is: Moodle asks whether to go on from the last page seen, and they do. */
|
|
67
|
+
async function openLesson(context, siteUrl, cmid) {
|
|
68
|
+
const page = await openSitePage(context, siteUrl, `mod/lesson/view.php?id=${cmid}`);
|
|
69
|
+
try {
|
|
70
|
+
if ((await page.locator("body#page-mod-lesson-view").count()) === 0) {
|
|
71
|
+
throw new LessonError(`Activity ${cmid} isn't a lesson you can open. Take the id of a lesson (module "lesson") from the course structure.`);
|
|
72
|
+
}
|
|
73
|
+
const resume = page.locator('#region-main a[href*="startlastseen=yes"]');
|
|
74
|
+
if ((await resume.count()) > 0)
|
|
75
|
+
await page.goto((await resume.first().getAttribute("href")), { waitUntil: "domcontentloaded" });
|
|
76
|
+
return page;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
await page.close().catch(() => undefined);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** The lesson page the person is on (starting the lesson the first time). */
|
|
84
|
+
export async function lessonPage(context, siteUrl, cmid) {
|
|
85
|
+
const page = await openLesson(context, siteUrl, cmid);
|
|
86
|
+
try {
|
|
87
|
+
return await readLessonPage(page);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
await page.close().catch(() => undefined);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Answers the page the person is on: a content page's button (its label or position), or a
|
|
95
|
+
* question's fields by name. Returns Moodle's feedback when it gives one, and the page that follows,
|
|
96
|
+
* going on past the feedback as its "Continue" does; reaching the end ends the attempt. With the
|
|
97
|
+
* realistic trail it first takes the time a reader needs for the page (ADR-005).
|
|
98
|
+
*/
|
|
99
|
+
export async function answerLessonPage(context, siteUrl, cmid, answer, { trail = "realistic", onProgress } = {}) {
|
|
100
|
+
const page = await openLesson(context, siteUrl, cmid);
|
|
101
|
+
try {
|
|
102
|
+
const current = await readLessonPage(page);
|
|
103
|
+
// What the answer can't be is told before the reading pause, not after it.
|
|
104
|
+
const buttons = current.buttons ?? [];
|
|
105
|
+
const index = typeof answer.button === "number" ? answer.button : buttons.findIndex((b) => b.label === answer.button);
|
|
106
|
+
if (current.kind === "content" && (answer.button === undefined || index < 0 || index >= buttons.length)) {
|
|
107
|
+
throw new LessonError(`This content page's buttons are: ${buttons.map((b, i) => `${i}: "${b.label}"`).join(", ")}. Give one by label or position.`);
|
|
108
|
+
}
|
|
109
|
+
if (current.kind === "question" && (!answer.responses || Object.keys(answer.responses).length === 0)) {
|
|
110
|
+
throw new LessonError("This page is a question: answer it with its fields' values (responses).");
|
|
111
|
+
}
|
|
112
|
+
if (current.kind === "end" || current.kind === "feedback") {
|
|
113
|
+
throw new LessonError(current.kind === "end" ? "The lesson has ended: there's nothing more to answer." : "This page only goes on: answer the page it leads to.");
|
|
114
|
+
}
|
|
115
|
+
if (trail === "realistic") {
|
|
116
|
+
const seconds = readingSeconds([current.text, ...(current.fields ?? []).flatMap((f) => [f.label ?? "", ...(f.options ?? []).map((o) => o.label)])].join(" "));
|
|
117
|
+
await pause(seconds, (elapsed) => onProgress?.(elapsed, seconds));
|
|
118
|
+
}
|
|
119
|
+
if (current.kind === "content") {
|
|
120
|
+
await Promise.all([page.waitForNavigation({ waitUntil: "domcontentloaded" }), page.locator('.branchbuttoncontainer form[action*="/mod/lesson/continue.php"] [type=submit], .branchbuttoncontainer form button').nth(index).click()]);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const form = 'form.mform[action*="/mod/lesson/continue.php"]';
|
|
124
|
+
await setFields(page, answer.responses, form);
|
|
125
|
+
await Promise.all([page.waitForNavigation({ waitUntil: "domcontentloaded" }), page.click(`${form} [name="submitbutton"]`)]);
|
|
126
|
+
}
|
|
127
|
+
let next = await readLessonPage(page);
|
|
128
|
+
let feedback;
|
|
129
|
+
if (next.kind === "feedback") {
|
|
130
|
+
feedback = next;
|
|
131
|
+
await Promise.all([page.waitForNavigation({ waitUntil: "domcontentloaded" }), page.click('#region-main form[action*="/mod/lesson/view.php"] [type=submit], #region-main form[action*="/mod/lesson/view.php"] button')]);
|
|
132
|
+
next = await readLessonPage(page);
|
|
133
|
+
}
|
|
134
|
+
return { ...(feedback ? { feedback } : {}), page: next };
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
await page.close().catch(() => undefined);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=lesson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lesson.js","sourceRoot":"","sources":["../../src/moodle/lesson.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BrowserContext, Page } from "playwright-core";
|
|
2
|
+
import type { BrowserManager } from "../browser.js";
|
|
3
|
+
export declare class LoginTimeoutError extends Error {
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class LoginWindowClosedError extends Error {
|
|
7
|
+
name: string;
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
/** The logged-in user, read from their own profile page (`user/profile.php`). */
|
|
11
|
+
export declare function readOwnProfile(context: BrowserContext, siteUrl: string): Promise<{
|
|
12
|
+
userId: number;
|
|
13
|
+
fullName: string;
|
|
14
|
+
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Waits until `page` shows the site with a user logged in. The person may go through other domains
|
|
17
|
+
* (SSO) on the way; reads fail while a page navigates, and are retried.
|
|
18
|
+
*/
|
|
19
|
+
export declare function waitForLogin(page: Page, siteUrl: string, { timeoutMs, pollMs, onWait }: {
|
|
20
|
+
timeoutMs: number;
|
|
21
|
+
pollMs?: number;
|
|
22
|
+
onWait?: (elapsedMs: number) => void;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
export interface LoginResult {
|
|
25
|
+
status: "already-logged-in" | "logged-in";
|
|
26
|
+
userId: number;
|
|
27
|
+
fullName: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Makes sure there's a session: the user at once if there is one; otherwise a browser window at the
|
|
31
|
+
* site's login page, waiting (calling `onWait`) until the person logs in there themselves. The session
|
|
32
|
+
* is saved (ADR-003), and the window closes again unless the browser is shown on demand.
|
|
33
|
+
*/
|
|
34
|
+
export declare function logIn(browser: BrowserManager, siteUrl: string, { timeoutMs, onWait }: {
|
|
35
|
+
timeoutMs: number;
|
|
36
|
+
onWait?: (elapsedMs: number) => void;
|
|
37
|
+
}): Promise<LoginResult>;
|