vease 1.54.0 → 1.55.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { waitForActionSettled } from "./wait_for_action_settled.js";
1
2
  import { navigateToApp } from "./navigate.js";
2
3
  import { expect, test as test$1 } from "@playwright/test";
3
4
  import { consola } from "consola";
@@ -66,7 +67,10 @@ const test = test$1.extend({
66
67
  autoScreenshot: [async ({ app, screenshotMask }, use, testInfo) => {
67
68
  await use(void 0);
68
69
  const { window } = app;
69
- if (testInfo.status === testInfo.expectedStatus) await expect(window).toHaveScreenshot({ mask: [window.getByTestId("accountNavButton"), ...screenshotMask.locators] });
70
+ if (testInfo.status === testInfo.expectedStatus) {
71
+ await waitForActionSettled(window);
72
+ await expect(window).toHaveScreenshot({ mask: [window.getByTestId("accountNavButton"), ...screenshotMask.locators] });
73
+ }
70
74
  }, { auto: true }]
71
75
  });
72
76
  //#endregion
@@ -4,13 +4,15 @@ function noopCleanup() {}
4
4
  const BUSY_INDICATOR_TEST_ID = "microservicesBusyIndicator";
5
5
  const REQUEST_DISPATCH_GRACE_MS = 50;
6
6
  const BUSY_INDICATOR_HARD_CEILING_MS = 1e4;
7
- async function waitForMicroservicesIdle(window, budgetMs) {
8
- await window.waitForTimeout(Math.min(REQUEST_DISPATCH_GRACE_MS, budgetMs));
7
+ async function waitForMicroservicesIdle(window, timeoutMs) {
9
8
  await window.getByTestId(BUSY_INDICATOR_TEST_ID).waitFor({
10
9
  state: "hidden",
11
- timeout: Math.max(budgetMs, BUSY_INDICATOR_HARD_CEILING_MS)
10
+ timeout: timeoutMs
12
11
  }).catch(noopCleanup);
13
12
  }
13
+ async function isMicroservicesBusy(window) {
14
+ return await window.getByTestId(BUSY_INDICATOR_TEST_ID).isVisible().catch(() => false);
15
+ }
14
16
  const MUTATION_QUIET_MS = 250;
15
17
  const MUTATION_POLL_INTERVAL_MS = 20;
16
18
  async function waitForDomSettled(window, budgetMs) {
@@ -45,10 +47,16 @@ async function waitForNextPaint(window) {
45
47
  });
46
48
  }).catch(noopCleanup);
47
49
  }
50
+ async function waitForIdleAndSettled(window, startedAt, ceilingMs, deadline) {
51
+ await waitForMicroservicesIdle(window, Math.max(1, deadline - Date.now()));
52
+ await waitForDomSettled(window, Math.max(MIN_DOM_SETTLE_BUDGET_MS, ceilingMs - (Date.now() - startedAt)));
53
+ if (Date.now() < deadline && await isMicroservicesBusy(window)) await waitForIdleAndSettled(window, startedAt, ceilingMs, deadline);
54
+ }
48
55
  async function waitForActionSettled(window, ceilingMs = afterActionWait) {
49
56
  const startedAt = Date.now();
50
- await waitForMicroservicesIdle(window, ceilingMs);
51
- await waitForDomSettled(window, Math.max(MIN_DOM_SETTLE_BUDGET_MS, ceilingMs - (Date.now() - startedAt)));
57
+ const deadline = startedAt + Math.max(ceilingMs, BUSY_INDICATOR_HARD_CEILING_MS);
58
+ await window.waitForTimeout(Math.min(REQUEST_DISPATCH_GRACE_MS, ceilingMs));
59
+ await waitForIdleAndSettled(window, startedAt, ceilingMs, deadline);
52
60
  await waitForNextPaint(window);
53
61
  }
54
62
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vease",
3
- "version": "1.54.0",
3
+ "version": "1.55.0-rc.2",
4
4
  "description": "Desktop and cloud software for data visualization",
5
5
  "keywords": [
6
6
  "Desktop",
@@ -88,12 +88,12 @@
88
88
  "@ai-sdk/vue": "4.0.93",
89
89
  "@fontsource/michroma": "5.2.6",
90
90
  "@fontsource/roboto": "5.2.10",
91
- "@geode/cloud-api": "latest",
92
- "@geode/opengeodeweb-back": "latest",
93
- "@geode/opengeodeweb-front": "latest",
94
- "@geode/opengeodeweb-viewer": "latest",
95
- "@geode/vease-back": "latest",
96
- "@geode/vease-viewer": "latest",
91
+ "@geode/cloud-api": "next",
92
+ "@geode/opengeodeweb-back": "next",
93
+ "@geode/opengeodeweb-front": "next",
94
+ "@geode/opengeodeweb-viewer": "next",
95
+ "@geode/vease-back": "next",
96
+ "@geode/vease-viewer": "next",
97
97
  "@nuxtjs/mcp-toolkit": "0.18.1",
98
98
  "ai": "7.0.93",
99
99
  "bowser": "2.14.1",
@@ -12,6 +12,7 @@ import { consola } from "consola";
12
12
 
13
13
  // Local imports
14
14
  import { navigateToApp } from "./navigate";
15
+ import { waitForActionSettled } from "./wait_for_action_settled";
15
16
 
16
17
  const MILLISECONDS_PER_SECOND = 1000;
17
18
 
@@ -133,6 +134,8 @@ const test = base.extend<TestFixtures, WorkerFixtures>({
133
134
  // Read at teardown: the test may have restarted the app
134
135
  const { window } = app;
135
136
  if (testInfo.status === testInfo.expectedStatus) {
137
+ // Never capture while the microservices are still busy (busy indicator bar, half-rendered viewer)
138
+ await waitForActionSettled(window);
136
139
  // The account icon depends on login state (only cloud is logged in)
137
140
  await expect(window).toHaveScreenshot({
138
141
  mask: [window.getByTestId("accountNavButton"), ...screenshotMask.locators],
@@ -21,14 +21,21 @@ const REQUEST_DISPATCH_GRACE_MS = 50;
21
21
  // Some backend RPCs (attribute-name lookups on a component with many items, for example) occasionally take several seconds under CI load, so the indicator gets generous headroom to clear.
22
22
  const BUSY_INDICATOR_HARD_CEILING_MS = 10_000;
23
23
 
24
- async function waitForMicroservicesIdle(window: Page, budgetMs: number): Promise<void> {
25
- await window.waitForTimeout(Math.min(REQUEST_DISPATCH_GRACE_MS, budgetMs));
24
+ async function waitForMicroservicesIdle(window: Page, timeoutMs: number): Promise<void> {
26
25
  await window
27
26
  .getByTestId(BUSY_INDICATOR_TEST_ID)
28
- .waitFor({ state: "hidden", timeout: Math.max(budgetMs, BUSY_INDICATOR_HARD_CEILING_MS) })
27
+ .waitFor({ state: "hidden", timeout: timeoutMs })
29
28
  .catch(noopCleanup);
30
29
  }
31
30
 
31
+ async function isMicroservicesBusy(window: Page): Promise<boolean> {
32
+ const busy = await window
33
+ .getByTestId(BUSY_INDICATOR_TEST_ID)
34
+ .isVisible()
35
+ .catch(() => false);
36
+ return busy;
37
+ }
38
+
32
39
  const MUTATION_QUIET_MS = 250;
33
40
  const MUTATION_POLL_INTERVAL_MS = 20;
34
41
 
@@ -73,11 +80,28 @@ async function waitForNextPaint(window: Page): Promise<void> {
73
80
  .catch(noopCleanup);
74
81
  }
75
82
 
76
- async function waitForActionSettled(window: Page, ceilingMs = afterActionWait): Promise<void> {
77
- const startedAt = Date.now();
78
- await waitForMicroservicesIdle(window, ceilingMs);
83
+ // Multi-step actions (project import, for example) chain several requests with short gaps in between: the indicator briefly hides, then shows again.
84
+ // The indeterminate bar animates through CSS only, so the DOM can look quiet while it is visible: re-check it after the DOM settles and start over if it came back.
85
+ async function waitForIdleAndSettled(
86
+ window: Page,
87
+ startedAt: number,
88
+ ceilingMs: number,
89
+ deadline: number,
90
+ ): Promise<void> {
91
+ // A zero timeout means "no timeout" in Playwright
92
+ await waitForMicroservicesIdle(window, Math.max(1, deadline - Date.now()));
79
93
  const remainingMs = Math.max(MIN_DOM_SETTLE_BUDGET_MS, ceilingMs - (Date.now() - startedAt));
80
94
  await waitForDomSettled(window, remainingMs);
95
+ if (Date.now() < deadline && (await isMicroservicesBusy(window))) {
96
+ await waitForIdleAndSettled(window, startedAt, ceilingMs, deadline);
97
+ }
98
+ }
99
+
100
+ async function waitForActionSettled(window: Page, ceilingMs = afterActionWait): Promise<void> {
101
+ const startedAt = Date.now();
102
+ const deadline = startedAt + Math.max(ceilingMs, BUSY_INDICATOR_HARD_CEILING_MS);
103
+ await window.waitForTimeout(Math.min(REQUEST_DISPATCH_GRACE_MS, ceilingMs));
104
+ await waitForIdleAndSettled(window, startedAt, ceilingMs, deadline);
81
105
  await waitForNextPaint(window);
82
106
  }
83
107