Skip to content

Commit b4802ca

Browse files
Add 2captcha ext. Test & fix part of OTV data entry.
1 parent ac1edc1 commit b4802ca

File tree

2 files changed

+98
-71
lines changed

2 files changed

+98
-71
lines changed

playwright.config.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ module.exports = defineConfig({
99
ignoreHTTPSErrors: true,
1010
bypassCSP: true,
1111
launchOptions: {
12-
args: ["--no-sandbox", "--disable-setuid-sandbox"],
12+
args: [
13+
"--no-sandbox",
14+
"--disable-setuid-sandbox",
15+
`--disable-extensions-except=2captcha-solver-3.4.0`,
16+
"--load-extension=2captcha-solver-3.4.0",
17+
],
1318
},
1419
proxy: process.env.PROXY_URL ? { server: process.env.PROXY_URL } : undefined,
1520
actionTimeout: 10 * 1000,

tests/appointment.test.js

Lines changed: 92 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const test = require("../src/test.js")({
2121
OTV_NATIONALITY: null,
2222
OTV_NATIONALITY_OF_FAMILY: null,
2323
OTV_LAST_NAME: null,
24+
OTV_REASON_TYPE: "Erwerbstätigkeit",
25+
OTV_REASON: "selbstständigen Tätigkeit - Erteilung",
2426
});
2527

2628
test("appointment", async ({ context, params }, testInfo) => {
@@ -44,46 +46,45 @@ test("appointment", async ({ context, params }, testInfo) => {
4446
lastName: params.OTV_LAST_NAME,
4547
});
4648
return;
47-
} else {
48-
const dateURLs = await getDateURLs(servicePage, {
49-
locations: params.APPOINTMENT_LOCATIONS,
50-
earliestDate: params.APPOINTMENT_EARLIEST_DATE,
51-
latestDate: params.APPOINTMENT_LATEST_DATE,
52-
});
53-
expect(dateURLs.length, "No available appointment dates").toBeGreaterThan(
54-
0
55-
);
49+
}
50+
const dateURLs = await getDateURLs(servicePage, {
51+
locations: params.APPOINTMENT_LOCATIONS,
52+
earliestDate: params.APPOINTMENT_EARLIEST_DATE,
53+
latestDate: params.APPOINTMENT_LATEST_DATE,
54+
});
55+
expect(dateURLs.length, "No available appointment dates").toBeGreaterThan(
56+
0
57+
);
5658

57-
const appointmentURLs = await getAppointmentURLs(context, dateURLs, {
58-
earliestTime: params.APPOINTMENT_EARLIEST_TIME,
59-
latestTime: params.APPOINTMENT_LATEST_TIME,
60-
});
61-
expect(
62-
appointmentURLs.length,
63-
"No available appointments on any appointment date"
64-
).toBeGreaterThan(0);
65-
66-
for (const appointmentURL of appointmentURLs) {
67-
try {
68-
await bookAppointment(
69-
context,
70-
appointmentURL,
71-
{
72-
mailSlurpAPIKey: params.MAILSLURP_API_KEY,
73-
mailSlurpInboxId: params.MAILSLURP_INBOX_ID,
74-
formName: params.FORM_NAME,
75-
formTakeSurvey: params.FORM_TAKE_SURVEY,
76-
formNote: params.FORM_NOTE,
77-
formPhone: params.FORM_PHONE,
78-
},
79-
testInfo
80-
);
81-
return;
82-
} catch (e) {
83-
logger.error(
84-
`Booking appointment failed at ${appointmentURL}: ${e.message}`
85-
);
86-
}
59+
const appointmentURLs = await getAppointmentURLs(context, dateURLs, {
60+
earliestTime: params.APPOINTMENT_EARLIEST_TIME,
61+
latestTime: params.APPOINTMENT_LATEST_TIME,
62+
});
63+
expect(
64+
appointmentURLs.length,
65+
"No available appointments on any appointment date"
66+
).toBeGreaterThan(0);
67+
68+
for (const appointmentURL of appointmentURLs) {
69+
try {
70+
await bookAppointment(
71+
context,
72+
appointmentURL,
73+
{
74+
mailSlurpAPIKey: params.MAILSLURP_API_KEY,
75+
mailSlurpInboxId: params.MAILSLURP_INBOX_ID,
76+
formName: params.FORM_NAME,
77+
formTakeSurvey: params.FORM_TAKE_SURVEY,
78+
formNote: params.FORM_NOTE,
79+
formPhone: params.FORM_PHONE,
80+
},
81+
testInfo
82+
);
83+
return;
84+
} catch (e) {
85+
logger.error(
86+
`Booking appointment failed at ${appointmentURL}: ${e.message}`
87+
);
8788
}
8889
}
8990
throw new Error("Booking failed for all appointments.");
@@ -130,9 +131,9 @@ async function getServicePage(page, url) {
130131
async function getDateURLs(page, { locations, earliestDate, latestDate }) {
131132
return await test
132133
.step("get date urls", async () => {
133-
page.on("load", async () => {
134-
await Promise.all([checkRateLimitExceeded(page), checkCaptcha(page)]);
135-
});
134+
// page.on("load", async () => {
135+
// await Promise.all([checkRateLimitExceeded(page), checkCaptcha(page)]);
136+
// });
136137
await selectLocations(page, {
137138
locations: locations ? locations.split(",") : [],
138139
});
@@ -562,44 +563,65 @@ async function otvAppointment(
562563
page.getByRole("heading", { name: "Sitzungsende" }),
563564
"Unexpectedly logged out"
564565
).not.toBeVisible({ timeout: 1 }),
565-
checkCaptcha(page),
566-
checkRateLimitExceeded(page),
567-
expect(
568-
page.getByText("aktuell keine Termine frei"),
569-
"No appointments currently available"
570-
).not.toBeVisible({ timeout: 1 }),
566+
// expect(
567+
// page.getByText("aktuell keine Termine frei"),
568+
// "No appointments currently available"
569+
// ).not.toBeVisible({ timeout: 1 }),
571570
]);
572571
});
573572
await page.getByRole("link", { name: "Termin buchen" }).click();
574573
await page.waitForURL("**/otv.verwalt-berlin.de/**");
575574
await page.getByRole("link", { name: "Termin buchen" }).click();
576-
await page.getByRole("checkbox").check();
575+
await page.waitForURL("https://otv.verwalt-berlin.de/ams/TerminBuchen");
576+
await page.getByRole("checkbox", { name: "Ich erkläre hiermit" }).check();
577577
await page.getByRole("button", { name: "Weiter" }).click();
578-
await page.getByLabel("Staatsangehörigkeit").selectOption(nationality);
579-
await page.getByLabel("Anzahl der Personen").selectOption(numberOfPeople);
580578
await page
581-
.getByLabel("mit einem Familienangehörigen")
582-
.selectOption(withFamily === "true" ? "1" : "2");
583-
if (withFamily) {
584-
await page
585-
.getByLabel("Staatsangehörigkeit des Familienangehörigen")
586-
.selectOption(familyNationality);
587-
}
588-
await page.getByLabel(serviceName).check();
589-
if (
590-
serviceName === "Aufenthaltstitel - beantragen" ||
591-
serviceName === "Aufenthaltstitel - verlängern"
592-
) {
593-
await page.getByLabel(residenceReasonType).check();
579+
.getByLabel("Staatsangehörigkeit (Wenn Sie")
580+
.selectOption(nationality);
581+
await page.locator("#xi-sel-422").selectOption(numberOfPeople);
582+
await expect(async () => {
583+
const withFamilyLocator = page.getByLabel(
584+
"Leben Sie in Berlin zusammen mit einem Familienangehörigen (z.B. Ehepartner, Kind)*",
585+
{ exact: true }
586+
);
587+
// Select the wrong option first to retry selecting the correct option.
588+
await withFamilyLocator.selectOption(withFamily === "true" ? "2" : "1");
589+
// Select the correct option.
590+
await withFamilyLocator.selectOption(withFamily === "true" ? "1" : "2");
591+
// TODO: familyNationalityLocator is untested so far.
592+
const familyNationalityLocator = page.getByLabel(
593+
"Staatsangehörigkeit des Familienangehörigen"
594+
);
595+
if (await familyNationalityLocator.isVisible()) {
596+
await familyNationalityLocator.selectOption(familyNationality);
597+
}
598+
await expect(page.getByLabel(serviceName)).toHaveCount(1);
599+
}).toPass();
600+
// TODO: serviceLocator is not visible for some reason.
601+
const serviceLocator = page.getByLabel(serviceName);
602+
await serviceLocator.check();
603+
// TODO: Everything below is untested so far.
604+
const residenceReasonTypeLocator = page.getByLabel(residenceReasonType);
605+
if (await residenceReasonTypeLocator.isVisible()) {
606+
await residenceReasonTypeLocator.check();
594607
}
595608
await page.getByLabel(residenceReason).check();
596-
if (
597-
residenceReason === "Aufenthaltsgestattung - verlängern" ||
598-
residenceReason === "Duldung - verlängern"
599-
) {
600-
await page.getByLabel("Nachnamen").fill(lastName);
609+
const lastNameLocator = page.getByLabel("Nachnamen");
610+
if (await lastNameLocator.isVisible()) {
611+
await lastNameLocator.fill(lastName);
601612
}
602613
await page.getByRole("button", { name: "Weiter" }).click();
603-
// TODO: If we actually get a calendar, book the appointment lol
614+
await page.getByRole("row").getByRole("link").first().click();
615+
await page
616+
.getByLabel("Bitte wählen Sie einen Tag")
617+
.selectOption({ label: /d{2}:\d{2}/ });
618+
// Solve captcha with 2Captcha chrome extension
619+
// TODO: Solve captcha with 2Captcha API
620+
await page.locator(".captcha-solver").click();
621+
await expect(
622+
page.locator(`.captcha-solver[data-state="solved"]`)
623+
).toBeVisible({ timeout: 150_000 });
624+
// Submit booking
625+
await page.getByRole("button", { name: "Weiter" }).click();
604626
});
605627
}

0 commit comments

Comments
 (0)