Skip to content

Playwright tests failing on timeout exceeded (again)#490

Open
conservationtimothy wants to merge 8 commits into
mainfrom
fix/failing-tests
Open

Playwright tests failing on timeout exceeded (again)#490
conservationtimothy wants to merge 8 commits into
mainfrom
fix/failing-tests

Conversation

@conservationtimothy

@conservationtimothy conservationtimothy commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Goal

Fix the Playwright failures, especially alerts dashboard map-readiness timeouts and RBAC/public-view CI behavior. Closes #441 .

Screenshots

Not applicable.

What I changed and why

I removed CI’s dependency on remote Mapbox style loading by having seed view configs use a minimal local Mapbox style, and updated map components to accept typed Mapbox style objects in addition to style URLs. The alerts dashboard now skips adding symbol label layers when the active style has no glyphs, keeping the local CI style usable while still rendering alert and Mapeo circle layers for tests.

I also made the map readiness signal more explicit. The concrete issue I was addressing was that our E2E readiness signal depended on an extra Mapbox idle event after the alert/Mapeo layers had already been added, which made test synchronization harder to reason about. Since prepareMapCanvasContent() already awaits alert/Mapeo layer setup and then calls prepareMapLegendContent() last, the legend can be built directly from the layers that were just added. I moved the test-only mapReady signal to the end of prepareMapCanvasContent() so it marks the actual readiness boundary: map data layers and legend content have been prepared.

Finally, I fixed an RBAC/public-view bug where /api/config/public_views read the public_views table directly instead of the seeded view rows used elsewhere in CI. That allowed middleware to deny a public dataset before the seeded ROUTE_LEVEL_PERMISSION: "anyone" config was applied. fetchPublicViewTableNames() now returns public datasets from the CI seed rows, with a unit regression test covering this behavior.

How I convinced myself this is right

The alert failures were all waiting for map UI that is created after Mapbox’s load path, so the most plausible common failure was not 15 independent Playwright flakes but the dashboard never reaching its loaded map state in CI. A minimal local style removes the remote style API from that path while still exercising the application’s own layer/source setup. The glyph guard is necessary because the minimal style intentionally has no glyph URL, and Mapbox symbol layers that render text require glyphs.

The final RBAC failure showed the guest public-dataset precondition returning denied, which contradicted the CI seed config. Following the middleware path showed it checks /api/config/public_views before route-level config, and that API did not have the CI fallback used elsewhere. The new unit test executes that behavior directly by setting CI=true and asserting the seeded public datasets are exposed.

What I'm not doing here

I improved the local Playwright workflow during investigation, but that is out of scope for this PR and was removed from this branch.

LLM use disclosure

GPT-5.5 helped investigate the Playwright failures, implement the fixes, and draft this PR description.

@conservationtimothy conservationtimothy force-pushed the fix/failing-tests branch 2 times, most recently from cf4cf04 to 6987847 Compare June 24, 2026 21:17
Use a local Mapbox style for CI seed views so alert dashboards do not depend on external style loading, and wait for concrete RBAC denial states.
Keep public dataset routing deterministic in CI and remove local Playwright workflow changes from this test-fix branch.

@rudokemper rudokemper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test solutions seem fine but I have questions about changes to production code on the Alerts Dashboard and in dbOperations, and a suggestion to tidy things up, along with a few other comments.

Comment thread components/AlertsDashboard.vue Outdated
Comment on lines +1360 to +1368
map.value.once("idle", () => {
const legendItems: MapLegendItem[] = [];
const legendItems: MapLegendItem[] = [];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I see happening here is that production code was adapted to make a test pass.

I see an explicit method invoked map.value.once("idle", () => {} that was added for reason replaced with a simple mapReady ref.

I don't see an explanation for this in the PR description.

Can you justify this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concrete issue I was addressing was that our E2E readiness signal depended on an extra Mapbox idle event after the alert/Mapeo layers had already been added, which made the test synchronization harder to reason about.

Since prepareMapCanvasContent() already awaits alert/Mapeo layer setup and then calls prepareMapLegendContent() last, the legend can be built directly from the layers that were just added. I’ve now moved the test-only mapReady signal to the end of prepareMapCanvasContent() so it marks the actual readiness boundary: map data layers and legend content have been prepared.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added to the PR description

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve now moved the test-only mapReady signal to the end of prepareMapCanvasContent() so it marks the actual readiness boundary: map data layers and legend content have been prepared.

That is fine. However:

The concrete issue I was addressing was that our E2E readiness signal depended on an extra Mapbox idle event after the alert/Mapeo layers had already been added, which made test synchronization harder to reason about.

Consider that this idle event was implemented for a reason, to ensure a certain behavior in production. If I remember correctly, so that the map legend doesn't render before all of the map layers have been drawn, and therefore an issue with missing layers in the legend.

I don't think production code should be changed in order to make tests happy. Consider that doing this, you might be breaking desired behavior.

At best, I would consider that to be a different PR. The scope of this PR, in my view, is to fix tests, not to modify anything in the runtime code aside from adding refs or test-only conditionals.

@conservationtimothy conservationtimothy Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough; I restored the existing idle behavior

Comment thread middleware/oauth.global.ts Outdated
Comment on lines +50 to +60
const response = await $fetch<{
views: Array<{
primaryDataset: string;
viewConfig: { ROUTE_LEVEL_PERMISSION?: RouteLevelPermission };
}>;
}>("/api/config");
const viewEntry = response.views?.find(
(v) => v.primaryDataset === tableName,
);
const permission: RouteLevelPermission =
tableConfig?.[tableName]?.ROUTE_LEVEL_PERMISSION ?? "member";
viewEntry?.viewConfig?.ROUTE_LEVEL_PERMISSION ?? "member";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this change to production code, that will break any Explorer app that hasn't already migrated to views from views_config? This seems entirely out of place in this PR that is just focused on improving tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original justification was that /api/config now returns view rows since we merged to main, so middleware needed to read ROUTE_LEVEL_PERMISSION from the matching view entry. But you’re right that the way I made that change was too narrow and could break Explorer apps that still receive views_config.

I’ve refactored this so the middleware supports both shapes: it first handles the new views response, and falls back.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good idea. This code supporting both shapes will soon become deprecated. Consider the mental load that this will soon add for a human reader (like myself, or a future developer). We might not know why it is that we handle both cases, long after the migration work is done. I will have to ask myself, and perhaps take time to investigate -- why is this code here, handling both shapes? Is it redundant or is there some edge case?

I'd rather we finish the migration work, and then implement this change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided to undo this. i hear you on the mental overhead, supporting both shapes adds a lot of that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current solution will still break any instance that doesn't already have the migration implemented (i.e. views table is empty or nonexistent).

https://github.com/ConservationMetrics/gc-explorer/pull/490/changes#diff-03a4d164f0bb92c9c8a41d2289634fb12b298e340055b9982f00f1bf5d3e7249R59

Either this PR cannot be merged until #459 is done, or you need to find a way to ensure the tests can pass without changing production code, which as stated a number of times, is my preference.

@@ -472,9 +472,20 @@ const buildCiViewConfigRows = (): ViewConfigRow[] => {
process.env.MAPBOX_ACCESS_TOKEN || "{MAPBOX_ACCESS_TOKEN}";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate PR, but I would love for this entire buildCiViewConfigRows to live in a different file. It is purely for CI and feels highly out of place in this already large file of production code.

Maybe move this to /tests/e2e/helpers/dbSeedHelpers.ts.

Comment on lines +475 to +485
const ciMapStyle: NonNullable<ViewConfig["MAPBOX_STYLE"]> = {
version: 8,
sources: {},
layers: [
{
id: "background",
type: "background",
paint: { "background-color": "#f8fafc" },
},
],
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good solution.

Comment thread components/AlertsDashboard.vue Outdated

@rudokemper rudokemper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Playwright tests failing on timeout exceeded (again)

2 participants