feat(clickTtPlayerRegistrationService): enhance registration flow with club context and error handling
- Integrated Club model to retrieve association member number during player registration, ensuring necessary data is available. - Added methods to select club context and ensure application entry visibility, improving navigation within the Click-TT interface. - Enhanced error handling for missing association member numbers, providing clearer feedback to users during the registration process. - Updated consent overlay handling to streamline user interactions and improve automation in the registration flow.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { chromium } from 'playwright';
|
||||
import Member from '../models/Member.js';
|
||||
import Club from '../models/Club.js';
|
||||
import ClickTtAccount from '../models/ClickTtAccount.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
@@ -46,6 +47,12 @@ class ClickTtPlayerRegistrationService {
|
||||
throw new HttpError('Mitglied nicht gefunden', 404);
|
||||
}
|
||||
|
||||
const club = await Club.findByPk(clubId);
|
||||
const associationMemberNumber = String(club?.associationMemberNumber || '').trim();
|
||||
if (!associationMemberNumber) {
|
||||
throw new HttpError('Für den Verein ist keine Verbands-Mitgliedsnummer hinterlegt', 400);
|
||||
}
|
||||
|
||||
const account = await ClickTtAccount.findOne({ where: { userId } });
|
||||
if (!account?.username) {
|
||||
throw new HttpError('Kein HTTV-/click-TT-Account für diesen Benutzer hinterlegt', 400);
|
||||
@@ -82,6 +89,8 @@ class ClickTtPlayerRegistrationService {
|
||||
this._attachNetworkLogging(page, trace);
|
||||
|
||||
await this._openAuthenticatedClickTt(page, { username, password, trace });
|
||||
await this._selectClubContext(page, associationMemberNumber, trace);
|
||||
await this._ensureApplicationEntryVisible(page, trace);
|
||||
await this._clickByText(page, 'Spielberechtigungen beantragen', trace);
|
||||
await this._fillSearchForm(page, memberJson);
|
||||
await this._clickByText(page, 'Personen suchen', trace);
|
||||
@@ -271,10 +280,13 @@ class ClickTtPlayerRegistrationService {
|
||||
|
||||
async _dismissConsentOverlays(page, trace) {
|
||||
const candidates = [
|
||||
page.getByRole('button', { name: /verstanden/i }).first(),
|
||||
page.getByRole('button', { name: /akzeptieren|accept|zustimmen/i }).first(),
|
||||
page.getByRole('link', { name: /verstanden/i }).first(),
|
||||
page.locator('#cmpwelcomebtnyes').first(),
|
||||
page.locator('[id*="cmp"][class*="button"]:has-text("Akzeptieren")').first(),
|
||||
page.locator('button:has-text("Akzeptieren")').first()
|
||||
page.locator('button:has-text("Akzeptieren")').first(),
|
||||
page.locator('a:has-text("Verstanden")').first()
|
||||
];
|
||||
|
||||
for (const locator of candidates) {
|
||||
@@ -293,6 +305,47 @@ class ClickTtPlayerRegistrationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
async _ensureApplicationEntryVisible(page, trace) {
|
||||
await this._dismissConsentOverlays(page, trace);
|
||||
|
||||
const directEntry = await this._hasTextTarget(page, 'Spielberechtigungen beantragen');
|
||||
if (directEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (await this._hasTextTarget(page, 'Persönlicher Bereich')) {
|
||||
this._trace(trace, 'step', {
|
||||
name: 'open-personal-area'
|
||||
});
|
||||
await this._clickByText(page, 'Persönlicher Bereich', trace);
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await this._dismissConsentOverlays(page, trace);
|
||||
}
|
||||
}
|
||||
|
||||
async _selectClubContext(page, associationMemberNumber, trace) {
|
||||
await this._dismissConsentOverlays(page, trace);
|
||||
|
||||
const clubLink = page.locator(`a:has-text("(${associationMemberNumber})")`).first();
|
||||
if (await clubLink.count()) {
|
||||
this._trace(trace, 'step', {
|
||||
name: 'select-club-context',
|
||||
associationMemberNumber
|
||||
});
|
||||
await clubLink.click();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await this._dismissConsentOverlays(page, trace);
|
||||
return true;
|
||||
}
|
||||
|
||||
this._trace(trace, 'step', {
|
||||
name: 'club-context-not-found-on-page',
|
||||
associationMemberNumber,
|
||||
url: page.url()
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
async _fillSearchForm(page, member) {
|
||||
await this._fillFirstAvailable(page, [
|
||||
'input[name*=".1"]',
|
||||
@@ -384,6 +437,26 @@ class ClickTtPlayerRegistrationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
async _hasTextTarget(page, text) {
|
||||
const escaped = escapeRegExp(text);
|
||||
const selectors = [
|
||||
`input[type="submit"][value*="${text}"]`,
|
||||
`input[type="button"][value*="${text}"]`,
|
||||
`button:has-text("${text}")`,
|
||||
`a:has-text("${text}")`,
|
||||
`text=/${escaped}/i`
|
||||
];
|
||||
|
||||
for (const selector of selectors) {
|
||||
const locator = page.locator(selector).first();
|
||||
if (await locator.count()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async _clickByText(page, text, trace) {
|
||||
const escaped = escapeRegExp(text);
|
||||
const selectors = [
|
||||
|
||||
Reference in New Issue
Block a user