refactor(clickTtPlayerRegistrationService, memberController): improve error handling and diagnostics
- Updated error response structure in memberController to include detailed information instead of a trace array, enhancing clarity for the client. - Enhanced ClickTtPlayerRegistrationService to capture and return detailed information about the selected search result and last submission attempt, improving error diagnostics. - Modified frontend to format and display the new detailed error information, providing better context for users during registration failures.
This commit is contained in:
@@ -102,6 +102,8 @@ class ClickTtPlayerRegistrationService {
|
||||
let context = null;
|
||||
let page = null;
|
||||
const trace = [];
|
||||
let selectedSearchResult = null;
|
||||
let lastSubmitResult = null;
|
||||
|
||||
try {
|
||||
browser = await chromium.launch({
|
||||
@@ -121,15 +123,18 @@ class ClickTtPlayerRegistrationService {
|
||||
await this._clickByText(page, 'Personen suchen', trace);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
|
||||
await this._openApplicationAfterSearch(page, memberJson, trace);
|
||||
selectedSearchResult = await this._openApplicationAfterSearch(page, memberJson, trace);
|
||||
await this._fillApplicationForm(page, memberJson, primaryEmail, trace);
|
||||
await this._clickByText(page, 'Weiter >>', trace);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
lastSubmitResult = await this._buildLastSubmitResult(page, 'Weiter >>');
|
||||
await this._dismissConsentOverlays(page, trace);
|
||||
await this._clickByText(page, 'Speichern', trace);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
lastSubmitResult = await this._buildLastSubmitResult(page, 'Speichern');
|
||||
await this._clickByText(page, 'Einreichen', trace);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
lastSubmitResult = await this._buildLastSubmitResult(page, 'Einreichen');
|
||||
|
||||
const finalText = sanitizePageText(await page.locator('body').innerText());
|
||||
const storageState = await context.storageState();
|
||||
@@ -145,14 +150,19 @@ class ClickTtPlayerRegistrationService {
|
||||
message: `Spielberechtigung fuer ${memberJson.firstName} ${memberJson.lastName} wurde in click-TT eingereicht.`,
|
||||
finalUrl: page.url(),
|
||||
finalText,
|
||||
trace
|
||||
details: {
|
||||
selectedSearchResult,
|
||||
lastSubmitResult
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
const diagnostics = {
|
||||
url: null,
|
||||
text: null,
|
||||
traceTail: trace.slice(-25),
|
||||
htmlPath: null
|
||||
htmlPath: null,
|
||||
selectedSearchResult,
|
||||
lastSubmitResult
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -191,6 +201,10 @@ class ClickTtPlayerRegistrationService {
|
||||
const wrappedError = new HttpError(`${message}${diagnostics.url ? ` (Seite: ${diagnostics.url})` : ''}${diagnostics.htmlPath ? ` (HTML: ${diagnostics.htmlPath})` : ''}${diagnostics.text ? ` - ${diagnostics.text}` : ''}`, error.statusCode || error.status || 500);
|
||||
wrappedError.trace = diagnostics.traceTail || [];
|
||||
wrappedError.htmlPath = diagnostics.htmlPath || null;
|
||||
wrappedError.details = {
|
||||
selectedSearchResult: diagnostics.selectedSearchResult || null,
|
||||
lastSubmitResult: diagnostics.lastSubmitResult || null
|
||||
};
|
||||
throw wrappedError;
|
||||
} finally {
|
||||
if (context) {
|
||||
@@ -484,7 +498,7 @@ class ClickTtPlayerRegistrationService {
|
||||
const expectedName = normalizeComparableText(`${member.lastName}, ${member.firstName}`);
|
||||
const expectedBirthDate = formatGermanDate(member.birthDate);
|
||||
|
||||
const explicitApplicationHref = await page.locator('table.result-set tr').evaluateAll((rows, criteria) => {
|
||||
const selectedResult = await page.locator('table.result-set tr').evaluateAll((rows, criteria) => {
|
||||
const normalize = (value) => String(value || '')
|
||||
.normalize('NFKC')
|
||||
.replace(/\s+/g, ' ')
|
||||
@@ -504,7 +518,12 @@ class ClickTtPlayerRegistrationService {
|
||||
if (nameText !== criteria.expectedName) continue;
|
||||
if (birthDateText !== normalize(criteria.expectedBirthDate)) continue;
|
||||
|
||||
return link.getAttribute('href');
|
||||
return {
|
||||
href: link.getAttribute('href'),
|
||||
name: String(strong.textContent || '').replace(/\s+/g, ' ').trim(),
|
||||
birthDate: String(cells[1]?.textContent || '').replace(/\s+/g, ' ').trim(),
|
||||
section: String(row.closest('table')?.querySelector('h2')?.textContent || '').replace(/\s+/g, ' ').trim()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -513,8 +532,8 @@ class ClickTtPlayerRegistrationService {
|
||||
expectedBirthDate
|
||||
});
|
||||
|
||||
if (explicitApplicationHref) {
|
||||
const explicitApplicationLink = page.locator(`a[href="${explicitApplicationHref}"]`).first();
|
||||
if (selectedResult?.href) {
|
||||
const explicitApplicationLink = page.locator(`a[href="${selectedResult.href}"]`).first();
|
||||
let dialogSeen = false;
|
||||
page.once('dialog', async (dialog) => {
|
||||
dialogSeen = true;
|
||||
@@ -528,9 +547,11 @@ class ClickTtPlayerRegistrationService {
|
||||
this._trace(trace, 'step', {
|
||||
name: 'click',
|
||||
label: 'Spielberechtigung beantragen',
|
||||
selector: `a[href="${explicitApplicationHref}"]`,
|
||||
selector: `a[href="${selectedResult.href}"]`,
|
||||
expectedName: `${member.lastName}, ${member.firstName}`,
|
||||
expectedBirthDate
|
||||
expectedBirthDate,
|
||||
selectedName: selectedResult.name,
|
||||
selectedBirthDate: selectedResult.birthDate
|
||||
});
|
||||
await explicitApplicationLink.click();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
@@ -540,7 +561,7 @@ class ClickTtPlayerRegistrationService {
|
||||
dialogSeen,
|
||||
url: page.url()
|
||||
});
|
||||
return;
|
||||
return selectedResult;
|
||||
}
|
||||
|
||||
const searchResultsText = sanitizePageText(await page.locator('table.result-set').innerText().catch(() => ''));
|
||||
@@ -552,6 +573,48 @@ class ClickTtPlayerRegistrationService {
|
||||
}
|
||||
}
|
||||
|
||||
async _buildLastSubmitResult(page, action) {
|
||||
return {
|
||||
action,
|
||||
url: page.url(),
|
||||
applicant: await this._readCurrentApplicant(page),
|
||||
pageText: sanitizePageText(await page.locator('body').innerText().catch(() => ''))
|
||||
};
|
||||
}
|
||||
|
||||
async _readCurrentApplicant(page) {
|
||||
return page.evaluate(() => {
|
||||
const form = document.querySelector('form.edit-object');
|
||||
if (!form) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const readValue = (selector) => {
|
||||
const field = form.querySelector(selector);
|
||||
if (!field) return '';
|
||||
if (field.tagName === 'SELECT') {
|
||||
const selected = field.options?.[field.selectedIndex];
|
||||
return String(selected?.textContent || '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
return String(field.value || '').replace(/\s+/g, ' ').trim();
|
||||
};
|
||||
|
||||
const lastName = readValue('input[name$=".15"], input[name$=".31.1.1.5.3"]');
|
||||
const firstName = readValue('input[name$=".17"], input[name$=".31.1.1.5.5"]');
|
||||
const birthDate = readValue('input[name$=".25"], input[name$=".31.1.1.5.11"]');
|
||||
|
||||
if (!lastName && !firstName && !birthDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
lastName,
|
||||
firstName,
|
||||
birthDate
|
||||
};
|
||||
}).catch(() => null);
|
||||
}
|
||||
|
||||
async _fillFirstAvailable(page, selectors, value) {
|
||||
for (const selector of selectors) {
|
||||
const locator = page.locator(selector).first();
|
||||
|
||||
Reference in New Issue
Block a user