From d0e92ca09b009d501b9e7f810dd0651c5bc8d221 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 5 Jan 2026 20:14:42 +0000
Subject: [PATCH 1/5] Initial plan
From 0f28a674d7f51515bc6dd5f748e7f68ba063e385 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 5 Jan 2026 20:20:02 +0000
Subject: [PATCH 2/5] Optimize sync endpoint and fix accessibility issues
- Replace unused existingFixtures variable with in-memory Map lookup
- Add batch database operations (batchUpdateFixture, batchCreateFixtureWithAvailability)
- Use batch operations instead of multiple sequential DB calls for better performance
- Remove role="button" from dialog backdrop (accessibility fix)
- Fix dialog keydown handler to allow Escape key propagation
- Replace arbitrary timeouts with proper Playwright waiting mechanisms in tests
- Remove unused syncingButton variable in tests
- Update test expectations to verify batch operations
Co-authored-by: chamika <754909+chamika@users.noreply.github.com>
---
frontend/e2e/availability-validation.test.ts | 31 +++----
.../routes/availability/[teamId]/+page.svelte | 4 +-
worker/src/database.ts | 83 +++++++++++++++++++
worker/src/index.test.ts | 71 ++++++++--------
worker/src/index.ts | 38 ++++-----
5 files changed, 152 insertions(+), 75 deletions(-)
diff --git a/frontend/e2e/availability-validation.test.ts b/frontend/e2e/availability-validation.test.ts
index 913c633..c0300be 100644
--- a/frontend/e2e/availability-validation.test.ts
+++ b/frontend/e2e/availability-validation.test.ts
@@ -262,13 +262,14 @@ test.describe('Player Summary Statistics', () => {
test('should show confirmation dialog when sync button clicked', async ({ page }) => {
// Navigate to Management tab
await page.waitForSelector('nav[aria-label="Tabs"]', { timeout: 10000 });
- await page.locator('button:has-text("Management")').click();
- await page.waitForTimeout(500);
+ const managementTab = page.locator('button:has-text("Management")');
+ await managementTab.click();
+ await managementTab.waitFor({ state: 'visible' });
// Click sync button
const syncButton = page.locator('button:has-text("Sync Fixtures")');
+ await syncButton.waitFor({ state: 'visible' });
await syncButton.click();
- await page.waitForTimeout(300);
// Check for confirmation dialog
const dialogHeading = page.locator('h3:has-text("Confirm Fixture Sync")');
@@ -282,7 +283,6 @@ test.describe('Player Summary Statistics', () => {
// Cancel the dialog
await cancelButton.click();
- await page.waitForTimeout(300);
// Dialog should be hidden
await expect(dialogHeading).not.toBeVisible();
@@ -291,25 +291,28 @@ test.describe('Player Summary Statistics', () => {
test('should show loading state during sync', async ({ page }) => {
// Navigate to Management tab
await page.waitForSelector('nav[aria-label="Tabs"]', { timeout: 10000 });
- await page.locator('button:has-text("Management")').click();
- await page.waitForTimeout(500);
+ const managementTab = page.locator('button:has-text("Management")');
+ await managementTab.click();
+ await managementTab.waitFor({ state: 'visible' });
// Click sync button
const syncButton = page.locator('button:has-text("Sync Fixtures")');
+ await syncButton.waitFor({ state: 'visible' });
await syncButton.click();
- await page.waitForTimeout(300);
// Click Sync Now
const syncNowButton = page.locator('button:has-text("Sync Now")');
+ await syncNowButton.waitFor({ state: 'visible' });
await syncNowButton.click();
- // Should show loading state (briefly)
- // Note: This might be too fast to catch in real scenarios
- const syncingButton = page.locator('button:has-text("Syncing...")');
- // We don't assert visibility because sync might complete too quickly
-
- // Wait for sync to complete - look for success notification or completion
- await page.waitForTimeout(2000);
+ // Wait for sync operation to complete by checking button state
+ // The button should either show "Syncing..." or go back to normal
+ await page.waitForFunction(() => {
+ const btn = document.querySelector('button:has-text("Sync Fixtures")');
+ return btn && !btn.textContent?.includes('Syncing');
+ }, { timeout: 5000 }).catch(() => {
+ // Sync might complete too quickly, which is fine
+ });
// After sync, button should be back to normal state
await expect(syncButton).toBeVisible();
diff --git a/frontend/src/routes/availability/[teamId]/+page.svelte b/frontend/src/routes/availability/[teamId]/+page.svelte
index 7548855..3c02ccf 100644
--- a/frontend/src/routes/availability/[teamId]/+page.svelte
+++ b/frontend/src/routes/availability/[teamId]/+page.svelte
@@ -435,8 +435,8 @@
{#if showSyncConfirm}
-
showSyncConfirm = false} onkeydown={(e) => e.key === 'Escape' && (showSyncConfirm = false)}>
-
e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
+
showSyncConfirm = false} onkeydown={(e) => e.key === 'Escape' && (showSyncConfirm = false)}>
+
e.stopPropagation()} onkeydown={(e) => { if (e.key !== 'Escape') e.stopPropagation(); }}>
Confirm Fixture Sync
diff --git a/worker/src/database.ts b/worker/src/database.ts
index 69afb29..4ceb32f 100644
--- a/worker/src/database.ts
+++ b/worker/src/database.ts
@@ -232,6 +232,89 @@ export class DatabaseService {
.run();
}
+ // Batch operations for sync
+ async batchUpdateFixture(
+ fixtureId: string,
+ matchDate: string,
+ dayTime: string,
+ playerIds: string[]
+ ): Promise
{
+ const isPast = isPastDate(matchDate) ? 1 : 0;
+ const timestamp = now();
+
+ // Build batch of statements
+ const statements = [
+ // Update fixture date
+ this.db.prepare('UPDATE fixtures SET match_date = ?, day_time = ?, is_past = ? WHERE id = ?')
+ .bind(matchDate, dayTime, isPast, fixtureId),
+ // Clear availability
+ this.db.prepare('DELETE FROM availability WHERE fixture_id = ?')
+ .bind(fixtureId),
+ // Clear final selections
+ this.db.prepare('DELETE FROM final_selections WHERE fixture_id = ?')
+ .bind(fixtureId),
+ ];
+
+ // Add availability inserts for each player
+ for (const playerId of playerIds) {
+ const availId = generateUUID();
+ statements.push(
+ this.db.prepare('INSERT INTO availability (id, fixture_id, player_id, is_available, updated_at) VALUES (?, ?, ?, ?, ?)')
+ .bind(availId, fixtureId, playerId, 0, timestamp)
+ );
+ }
+
+ // Execute all statements in a batch
+ await this.db.batch(statements);
+ }
+
+ async batchCreateFixtureWithAvailability(
+ teamId: string,
+ matchDate: string,
+ dayTime: string,
+ homeTeam: string,
+ awayTeam: string,
+ venue: string | undefined,
+ playerIds: string[]
+ ): Promise {
+ const fixtureId = generateUUID();
+ const timestamp = now();
+ const isPast = isPastDate(matchDate) ? 1 : 0;
+
+ // Build batch of statements
+ const statements = [
+ // Create fixture
+ this.db.prepare(`
+ INSERT INTO fixtures (id, team_id, match_date, day_time, home_team, away_team, venue, is_past, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `).bind(fixtureId, teamId, matchDate, dayTime, homeTeam, awayTeam, venue || null, isPast, timestamp),
+ ];
+
+ // Add availability inserts for each player
+ for (const playerId of playerIds) {
+ const availId = generateUUID();
+ statements.push(
+ this.db.prepare('INSERT INTO availability (id, fixture_id, player_id, is_available, updated_at) VALUES (?, ?, ?, ?, ?)')
+ .bind(availId, fixtureId, playerId, 0, timestamp)
+ );
+ }
+
+ // Execute all statements in a batch
+ await this.db.batch(statements);
+
+ return {
+ id: fixtureId,
+ team_id: teamId,
+ match_date: matchDate,
+ day_time: dayTime,
+ home_team: homeTeam,
+ away_team: awayTeam,
+ venue: venue || null,
+ is_past: isPast,
+ created_at: timestamp
+ };
+ }
+
async getFinalSelections(teamId: string): Promise {
const result = await this.db
.prepare(`
diff --git a/worker/src/index.test.ts b/worker/src/index.test.ts
index 4c7c1c3..c63caac 100644
--- a/worker/src/index.test.ts
+++ b/worker/src/index.test.ts
@@ -67,7 +67,9 @@ describe('POST /api/availability/:teamId/sync', () => {
clearAvailabilityForFixture: vi.fn(),
clearFinalSelections: vi.fn(),
createAvailability: vi.fn(),
- createFixture: vi.fn()
+ createFixture: vi.fn(),
+ batchUpdateFixture: vi.fn(),
+ batchCreateFixtureWithAvailability: vi.fn()
};
// Mock DatabaseService constructor
@@ -181,25 +183,20 @@ describe('POST /api/availability/:teamId/sync', () => {
message: 'Sync completed: 1 updated, 0 new, 1 unchanged'
});
- // Verify update was made for the changed fixture
- expect(mockDbInstance.updateFixtureDate).toHaveBeenCalledTimes(1);
- expect(mockDbInstance.updateFixtureDate).toHaveBeenCalledWith(
+ // Verify batch update was made for the changed fixture
+ expect(mockDbInstance.batchUpdateFixture).toHaveBeenCalledTimes(1);
+ expect(mockDbInstance.batchUpdateFixture).toHaveBeenCalledWith(
'fixture-1',
'2026-01-16',
- 'Jan 16 Thu 19:00'
+ 'Jan 16 Thu 19:00',
+ ['player-1', 'player-2', 'player-3']
);
- // Verify availability was cleared and reinitialized
- expect(mockDbInstance.clearAvailabilityForFixture).toHaveBeenCalledTimes(1);
- expect(mockDbInstance.clearAvailabilityForFixture).toHaveBeenCalledWith('fixture-1');
- expect(mockDbInstance.clearFinalSelections).toHaveBeenCalledTimes(1);
- expect(mockDbInstance.clearFinalSelections).toHaveBeenCalledWith('fixture-1');
-
- // Verify availability was created for all players (3 players)
- expect(mockDbInstance.createAvailability).toHaveBeenCalledTimes(3);
- expect(mockDbInstance.createAvailability).toHaveBeenCalledWith('fixture-1', 'player-1', false);
- expect(mockDbInstance.createAvailability).toHaveBeenCalledWith('fixture-1', 'player-2', false);
- expect(mockDbInstance.createAvailability).toHaveBeenCalledWith('fixture-1', 'player-3', false);
+ // Verify individual methods were not called (we use batch now)
+ expect(mockDbInstance.updateFixtureDate).not.toHaveBeenCalled();
+ expect(mockDbInstance.clearAvailabilityForFixture).not.toHaveBeenCalled();
+ expect(mockDbInstance.clearFinalSelections).not.toHaveBeenCalled();
+ expect(mockDbInstance.createAvailability).not.toHaveBeenCalled();
});
it('should update availability when there are 2 fixture date changes', async () => {
@@ -249,25 +246,26 @@ describe('POST /api/availability/:teamId/sync', () => {
message: 'Sync completed: 2 updated, 0 new, 0 unchanged'
});
- // Verify updates were made for both fixtures
- expect(mockDbInstance.updateFixtureDate).toHaveBeenCalledTimes(2);
- expect(mockDbInstance.updateFixtureDate).toHaveBeenCalledWith(
+ // Verify batch updates were made for both fixtures
+ expect(mockDbInstance.batchUpdateFixture).toHaveBeenCalledTimes(2);
+ expect(mockDbInstance.batchUpdateFixture).toHaveBeenCalledWith(
'fixture-1',
'2026-01-16',
- 'Jan 16 Thu 19:00'
+ 'Jan 16 Thu 19:00',
+ ['player-1', 'player-2', 'player-3']
);
- expect(mockDbInstance.updateFixtureDate).toHaveBeenCalledWith(
+ expect(mockDbInstance.batchUpdateFixture).toHaveBeenCalledWith(
'fixture-2',
'2026-01-23',
- 'Jan 23 Thu 18:45'
+ 'Jan 23 Thu 18:45',
+ ['player-1', 'player-2', 'player-3']
);
- // Verify availability was cleared for both fixtures
- expect(mockDbInstance.clearAvailabilityForFixture).toHaveBeenCalledTimes(2);
- expect(mockDbInstance.clearFinalSelections).toHaveBeenCalledTimes(2);
-
- // Verify availability was created for all players for both fixtures (3 players × 2 fixtures = 6 calls)
- expect(mockDbInstance.createAvailability).toHaveBeenCalledTimes(6);
+ // Verify individual methods were not called (we use batch now)
+ expect(mockDbInstance.updateFixtureDate).not.toHaveBeenCalled();
+ expect(mockDbInstance.clearAvailabilityForFixture).not.toHaveBeenCalled();
+ expect(mockDbInstance.clearFinalSelections).not.toHaveBeenCalled();
+ expect(mockDbInstance.createAvailability).not.toHaveBeenCalled();
});
it('should return error when the ELTTL URL call fails', async () => {
@@ -375,21 +373,20 @@ describe('POST /api/availability/:teamId/sync', () => {
message: 'Sync completed: 1 new fixtures added, 1 unchanged'
});
- // Verify new fixture was created
- expect(mockDbInstance.createFixture).toHaveBeenCalledTimes(1);
- expect(mockDbInstance.createFixture).toHaveBeenCalledWith(
+ // Verify batch create was called for the new fixture
+ expect(mockDbInstance.batchCreateFixtureWithAvailability).toHaveBeenCalledTimes(1);
+ expect(mockDbInstance.batchCreateFixtureWithAvailability).toHaveBeenCalledWith(
mockTeamId,
'2026-01-29',
'Jan 29 Wed 18:45',
'Test Team',
'Opposition C',
- 'VENUE1'
+ 'VENUE1',
+ ['player-1', 'player-2', 'player-3']
);
- // Verify availability was initialized for all players for the new fixture
- expect(mockDbInstance.createAvailability).toHaveBeenCalledTimes(3);
- expect(mockDbInstance.createAvailability).toHaveBeenCalledWith('fixture-3', 'player-1', false);
- expect(mockDbInstance.createAvailability).toHaveBeenCalledWith('fixture-3', 'player-2', false);
- expect(mockDbInstance.createAvailability).toHaveBeenCalledWith('fixture-3', 'player-3', false);
+ // Verify individual methods were not called (we use batch now)
+ expect(mockDbInstance.createFixture).not.toHaveBeenCalled();
+ expect(mockDbInstance.createAvailability).not.toHaveBeenCalled();
});
});
diff --git a/worker/src/index.ts b/worker/src/index.ts
index 16c2f88..82af993 100644
--- a/worker/src/index.ts
+++ b/worker/src/index.ts
@@ -142,8 +142,13 @@ app.post('/api/availability/:teamId/sync', async (c) => {
fixtureCount: scrapedData.fixtures.length
});
- // Get existing fixtures
+ // Get existing fixtures and load into memory for efficient lookups
const existingFixtures = await db.getFixtures(teamId);
+ const fixtureMap = new Map();
+ for (const fixture of existingFixtures) {
+ const key = `${fixture.home_team}|${fixture.away_team}`;
+ fixtureMap.set(key, fixture);
+ }
let fixturesUpdated = 0;
let fixturesUnchanged = 0;
@@ -152,14 +157,16 @@ app.post('/api/availability/:teamId/sync', async (c) => {
// Get all players for availability initialization
const players = await db.getPlayers(teamId);
+ const playerIds = players.map(p => p.id);
// Process each scraped fixture
for (const scraped of scrapedData.fixtures) {
const matchDate = parseMatchDate(scraped.date);
const dayTime = `${scraped.date} ${scraped.time}`;
- // Try to match with existing fixture
- const existingFixture = await db.getFixtureByTeams(teamId, scraped.homeTeam, scraped.awayTeam);
+ // Try to match with existing fixture using in-memory lookup
+ const key = `${scraped.homeTeam}|${scraped.awayTeam}`;
+ const existingFixture = fixtureMap.get(key);
if (existingFixture) {
// Check if date has changed
@@ -172,17 +179,8 @@ app.post('/api/availability/:teamId/sync', async (c) => {
awayTeam: scraped.awayTeam
});
- // Update fixture date
- await db.updateFixtureDate(existingFixture.id, matchDate, dayTime);
-
- // Clear availability and selections
- await db.clearAvailabilityForFixture(existingFixture.id);
- await db.clearFinalSelections(existingFixture.id);
-
- // Reinitialize availability for all players (default to false)
- for (const player of players) {
- await db.createAvailability(existingFixture.id, player.id, false);
- }
+ // Use batch operation to update fixture, clear data, and reinitialize availability
+ await db.batchUpdateFixture(existingFixture.id, matchDate, dayTime, playerIds);
fixturesUpdated++;
updatedFixtureIds.push(existingFixture.id);
@@ -190,27 +188,23 @@ app.post('/api/availability/:teamId/sync', async (c) => {
fixturesUnchanged++;
}
} else {
- // New fixture - create it
+ // New fixture - create it with availability in a batch
log('info', 'New fixture found', {
homeTeam: scraped.homeTeam,
awayTeam: scraped.awayTeam,
matchDate
});
- const newFixture = await db.createFixture(
+ await db.batchCreateFixtureWithAvailability(
teamId,
matchDate,
dayTime,
scraped.homeTeam,
scraped.awayTeam,
- scraped.venue
+ scraped.venue,
+ playerIds
);
- // Initialize availability for all players (default to false)
- for (const player of players) {
- await db.createAvailability(newFixture.id, player.id, false);
- }
-
fixturesNew++;
}
}
From 7cdc514ffd5388dc0ed14fabeb5e9591cb63ddbb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 5 Jan 2026 20:21:23 +0000
Subject: [PATCH 3/5] Add role="presentation" to dialog backdrop for better
accessibility
Co-authored-by: chamika <754909+chamika@users.noreply.github.com>
---
frontend/package-lock.json | 109 ++++++++++++++----
.../routes/availability/[teamId]/+page.svelte | 2 +-
2 files changed, 90 insertions(+), 21 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 62d026e..66e8e73 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -51,6 +51,7 @@
"resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.1.tgz",
"integrity": "sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==",
"dev": true,
+ "peer": true,
"dependencies": {
"mime": "^3.0.0"
},
@@ -63,6 +64,7 @@
"resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.11.tgz",
"integrity": "sha512-se23f1D4PxKrMKOq+Stz+Yn7AJ9ITHcEecXo2Yjb+UgbUDCEBch1FXQC6hx6uT5fNA3kmX3mfzeZiUmpK1W9IQ==",
"dev": true,
+ "peer": true,
"peerDependencies": {
"unenv": "2.0.0-rc.24",
"workerd": "^1.20251106.1"
@@ -85,6 +87,7 @@
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": ">=16"
}
@@ -101,6 +104,7 @@
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": ">=16"
}
@@ -117,6 +121,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=16"
}
@@ -133,6 +138,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=16"
}
@@ -149,6 +155,7 @@
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": ">=16"
}
@@ -157,14 +164,14 @@
"version": "4.20251202.0",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20251202.0.tgz",
"integrity": "sha512-Q7m1Ivu2fbKalOPm00KLpu6GfRaq4TlrPknqugvZgp/gDH96OYKINO4x7jvCIBvCz/aK9vVoOj8tlbSQBervVA==",
- "dev": true,
- "peer": true
+ "dev": true
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
@@ -177,6 +184,7 @@
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
@@ -876,6 +884,7 @@
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -898,6 +907,7 @@
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -920,6 +930,7 @@
"os": [
"darwin"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -936,6 +947,7 @@
"os": [
"darwin"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -952,6 +964,7 @@
"os": [
"linux"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -968,6 +981,7 @@
"os": [
"linux"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -984,6 +998,7 @@
"os": [
"linux"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -1000,6 +1015,7 @@
"os": [
"linux"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -1016,6 +1032,7 @@
"os": [
"linux"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -1032,6 +1049,7 @@
"os": [
"linux"
],
+ "peer": true,
"funding": {
"url": "https://opencollective.com/libvips"
}
@@ -1048,6 +1066,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1070,6 +1089,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1092,6 +1112,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1114,6 +1135,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1136,6 +1158,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1158,6 +1181,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1177,6 +1201,7 @@
],
"dev": true,
"optional": true,
+ "peer": true,
"dependencies": {
"@emnapi/runtime": "^1.2.0"
},
@@ -1199,6 +1224,7 @@
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1218,6 +1244,7 @@
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0"
},
@@ -1303,6 +1330,7 @@
"resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz",
"integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==",
"dev": true,
+ "peer": true,
"dependencies": {
"kleur": "^4.1.5"
}
@@ -1312,6 +1340,7 @@
"resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",
"integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",
"dev": true,
+ "peer": true,
"dependencies": {
"@poppinss/colors": "^4.1.5",
"@sindresorhus/is": "^7.0.2",
@@ -1323,6 +1352,7 @@
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
"integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -1334,7 +1364,8 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz",
"integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.3",
@@ -1649,6 +1680,7 @@
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz",
"integrity": "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -1660,7 +1692,8 @@
"version": "1.2.12",
"resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.12.tgz",
"integrity": "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
@@ -1709,7 +1742,6 @@
"integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@standard-schema/spec": "^1.0.0",
"@sveltejs/acorn-typescript": "^1.0.5",
@@ -1749,7 +1781,6 @@
"integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^5.0.0",
"debug": "^4.4.1",
@@ -2099,7 +2130,6 @@
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -2150,7 +2180,6 @@
"integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.48.0",
"@typescript-eslint/types": "8.48.0",
@@ -2479,7 +2508,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2502,6 +2530,7 @@
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=0.4.0"
}
@@ -2635,7 +2664,8 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
"integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/brace-expansion": {
"version": "1.1.12",
@@ -2668,7 +2698,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -2772,6 +2801,7 @@
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"dev": true,
+ "peer": true,
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
@@ -2805,6 +2835,7 @@
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"dev": true,
+ "peer": true,
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
@@ -2933,6 +2964,7 @@
"resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
"integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
"dev": true,
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/antfu"
}
@@ -3015,7 +3047,6 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3230,6 +3261,7 @@
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz",
"integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=6"
},
@@ -3383,7 +3415,8 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/globals": {
"version": "16.5.0",
@@ -3463,7 +3496,8 @@
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/is-extglob": {
"version": "2.1.1",
@@ -3916,6 +3950,7 @@
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"dev": true,
+ "peer": true,
"bin": {
"mime": "cli.js"
},
@@ -3928,6 +3963,7 @@
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251125.0.tgz",
"integrity": "sha512-xY6deLx0Drt8GfGG2Fv0fHUocHAIG/Iv62Kl36TPfDzgq7/+DQ5gYNisxnmyISQdA/sm7kOvn2XRBncxjWYrLg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@cspotcode/source-map-support": "0.8.1",
"acorn": "8.14.0",
@@ -3954,6 +3990,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -4142,7 +4179,8 @@
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/pathe": {
"version": "2.0.3",
@@ -4163,7 +4201,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -4223,7 +4260,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -4482,6 +4518,7 @@
"integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
"dev": true,
"hasInstallScript": true,
+ "peer": true,
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.3",
@@ -4550,6 +4587,7 @@
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"dev": true,
+ "peer": true,
"dependencies": {
"is-arrayish": "^0.3.1"
}
@@ -4598,6 +4636,7 @@
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=4",
"npm": ">=6"
@@ -4635,7 +4674,6 @@
"integrity": "sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@jridgewell/remapping": "^2.3.4",
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -4881,7 +4919,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4919,6 +4956,7 @@
"resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz",
"integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=20.18.1"
}
@@ -4994,7 +5032,6 @@
"integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -5259,6 +5296,7 @@
"resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.51.0.tgz",
"integrity": "sha512-JHv+58UxM2//e4kf9ASDwg016xd/OdDNDUKW6zLQyE7Uc9ayYKX1QJ9NsYtpo4dC1dfg6rT67pf1aNK1cTzUDg==",
"dev": true,
+ "peer": true,
"dependencies": {
"@cloudflare/kv-asset-handler": "0.4.1",
"@cloudflare/unenv-preset": "2.7.11",
@@ -5300,6 +5338,7 @@
"os": [
"aix"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5316,6 +5355,7 @@
"os": [
"android"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5332,6 +5372,7 @@
"os": [
"android"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5348,6 +5389,7 @@
"os": [
"android"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5364,6 +5406,7 @@
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5380,6 +5423,7 @@
"os": [
"darwin"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5396,6 +5440,7 @@
"os": [
"freebsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5412,6 +5457,7 @@
"os": [
"freebsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5428,6 +5474,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5444,6 +5491,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5460,6 +5508,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5476,6 +5525,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5492,6 +5542,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5508,6 +5559,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5524,6 +5576,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5540,6 +5593,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5556,6 +5610,7 @@
"os": [
"linux"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5572,6 +5627,7 @@
"os": [
"netbsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5588,6 +5644,7 @@
"os": [
"netbsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5604,6 +5661,7 @@
"os": [
"openbsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5620,6 +5678,7 @@
"os": [
"openbsd"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5636,6 +5695,7 @@
"os": [
"sunos"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5652,6 +5712,7 @@
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5668,6 +5729,7 @@
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5684,6 +5746,7 @@
"os": [
"win32"
],
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -5694,6 +5757,7 @@
"integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
"dev": true,
"hasInstallScript": true,
+ "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -5733,6 +5797,7 @@
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=10.0.0"
},
@@ -5767,6 +5832,7 @@
"resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz",
"integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@poppinss/colors": "^4.1.5",
"@poppinss/dumper": "^0.6.4",
@@ -5780,6 +5846,7 @@
"resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz",
"integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==",
"dev": true,
+ "peer": true,
"dependencies": {
"@poppinss/exception": "^1.2.2",
"error-stack-parser-es": "^1.0.5"
@@ -5790,6 +5857,7 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"dev": true,
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -5810,6 +5878,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz",
"integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==",
"dev": true,
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/frontend/src/routes/availability/[teamId]/+page.svelte b/frontend/src/routes/availability/[teamId]/+page.svelte
index 3c02ccf..c79e3cb 100644
--- a/frontend/src/routes/availability/[teamId]/+page.svelte
+++ b/frontend/src/routes/availability/[teamId]/+page.svelte
@@ -435,7 +435,7 @@
{#if showSyncConfirm}
- showSyncConfirm = false} onkeydown={(e) => e.key === 'Escape' && (showSyncConfirm = false)}>
+
showSyncConfirm = false} onkeydown={(e) => e.key === 'Escape' && (showSyncConfirm = false)}>
e.stopPropagation()} onkeydown={(e) => { if (e.key !== 'Escape') e.stopPropagation(); }}>
Confirm Fixture Sync
From c24abafca3d764b8e71252752d61974a521e2cf2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 5 Jan 2026 20:22:22 +0000
Subject: [PATCH 4/5] Fix invalid CSS selector in test waitForFunction
Co-authored-by: chamika <754909+chamika@users.noreply.github.com>
---
frontend/e2e/availability-validation.test.ts | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/frontend/e2e/availability-validation.test.ts b/frontend/e2e/availability-validation.test.ts
index c0300be..1776715 100644
--- a/frontend/e2e/availability-validation.test.ts
+++ b/frontend/e2e/availability-validation.test.ts
@@ -305,11 +305,12 @@ test.describe('Player Summary Statistics', () => {
await syncNowButton.waitFor({ state: 'visible' });
await syncNowButton.click();
- // Wait for sync operation to complete by checking button state
- // The button should either show "Syncing..." or go back to normal
+ // Wait for sync operation to complete by waiting for button to not be disabled
+ // The button might briefly show "Syncing..." before completing
await page.waitForFunction(() => {
- const btn = document.querySelector('button:has-text("Sync Fixtures")');
- return btn && !btn.textContent?.includes('Syncing');
+ const buttons = Array.from(document.querySelectorAll('button'));
+ const syncBtn = buttons.find(btn => btn.textContent?.includes('Sync Fixtures'));
+ return syncBtn && !syncBtn.textContent?.includes('Syncing');
}, { timeout: 5000 }).catch(() => {
// Sync might complete too quickly, which is fine
});
From ede658a4c262f862a2c6b147daa69ba9e5f83c8f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 5 Jan 2026 20:23:02 +0000
Subject: [PATCH 5/5] Use proper Playwright locator for waiting on sync button
state
Co-authored-by: chamika <754909+chamika@users.noreply.github.com>
---
frontend/e2e/availability-validation.test.ts | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/frontend/e2e/availability-validation.test.ts b/frontend/e2e/availability-validation.test.ts
index 1776715..a677fe5 100644
--- a/frontend/e2e/availability-validation.test.ts
+++ b/frontend/e2e/availability-validation.test.ts
@@ -305,15 +305,16 @@ test.describe('Player Summary Statistics', () => {
await syncNowButton.waitFor({ state: 'visible' });
await syncNowButton.click();
- // Wait for sync operation to complete by waiting for button to not be disabled
- // The button might briefly show "Syncing..." before completing
- await page.waitForFunction(() => {
- const buttons = Array.from(document.querySelectorAll('button'));
- const syncBtn = buttons.find(btn => btn.textContent?.includes('Sync Fixtures'));
- return syncBtn && !syncBtn.textContent?.includes('Syncing');
- }, { timeout: 5000 }).catch(() => {
- // Sync might complete too quickly, which is fine
- });
+ // Wait for sync operation to complete
+ // Either the button will briefly show "Syncing..." or sync completes immediately
+ try {
+ // Try to wait for the syncing state to appear and then disappear
+ const syncingButton = page.locator('button:has-text("Syncing...")');
+ await syncingButton.waitFor({ state: 'visible', timeout: 1000 });
+ await syncingButton.waitFor({ state: 'hidden', timeout: 5000 });
+ } catch {
+ // Sync might complete too quickly to catch the intermediate state
+ }
// After sync, button should be back to normal state
await expect(syncButton).toBeVisible();