Skip to content

Commit bce8432

Browse files
authored
Fix nightly, improve browser feature check and misc other improvements (#8702)
- wkorg screenshot test failed because of changed view configuration defaults --> the wkorg test now overwrites the defaults on each run. this should be in our best interest, anyway, because it ensures good defaults. the downside is that we have to adapt the code if we want to tune the default. - flatMap was not supported by the browser used on browserstack. I fixed this by specifying a newer osx version. - flatMap on generators is now checked in the browser feature tests so that users are properly alerted about a necessary browser update - errors in the model initialization are detected in the screenshot tests now so that there is a better error message (and an eager quit on errors). until now, the test just waited until the timeout was exceeded. - there were two problems with float32 datasets: - the "auto clipping" feature did not work with infinite values (which exist in the float32 test dataset) - osx ventura did have shader problems with wide float32 ranges. using sequoia fixes this. ### URL of deployed dev instance (used for testing): - https://___.webknossos.xyz ### Steps to test: - see this run https://github.com/scalableminds/webknossos/actions/runs/15821624973 ### Issues: - fixes failing nightly ------ (Please delete unneeded items, merge only when none are left open) - [X] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`)
1 parent 573bcbe commit bce8432

File tree

14 files changed

+129
-46
lines changed

14 files changed

+129
-46
lines changed

frontend/javascripts/admin/rest_api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,10 +1068,12 @@ export function getDatasetDefaultConfiguration(datasetId: string): Promise<Datas
10681068
export function updateDatasetDefaultConfiguration(
10691069
datasetId: string,
10701070
datasetConfiguration: DatasetConfiguration,
1071+
options?: RequestOptions,
10711072
): Promise<ArbitraryObject> {
10721073
return Request.sendJSONReceiveJSON(`/api/datasetConfigurations/default/${datasetId}`, {
10731074
method: "PUT",
10741075
data: datasetConfiguration,
1076+
...options,
10751077
});
10761078
}
10771079

frontend/javascripts/components/brain_spinner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export function BrainSpinnerWithError({
7575
<BrainSpinner
7676
message={
7777
<>
78-
<div style={{ textAlign: "center" }}>
78+
<div className="initialization-error-message" style={{ textAlign: "center" }}>
7979
{gotUnhandledError ? messages["tracing.unhandled_initialization_error"] : message}
8080
</div>
8181
<div className="flex-center-child" style={{ gap: 8 }}>

frontend/javascripts/libs/browser_feature_check.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default function checkBrowserFeatures() {
1111
new Map([[1, 2]]).values().map((v) => v);
1212
[].at(0);
1313
new Set().difference(new Set());
14+
[].values().flatMap((el) => [el, el]);
1415
} catch (exception) {
1516
Toast.warning(
1617
<div>

frontend/javascripts/main.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,6 @@ async function loadOrganization() {
8686
Store.dispatch(setActiveOrganizationAction(organization));
8787
}
8888
}
89-
9089
document.addEventListener("DOMContentLoaded", async () => {
9190
ErrorHandling.initialize({
9291
throwAssertions: false,
Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from "node:path";
22
import { compareScreenshot, isPixelEquivalent } from "./screenshot_helpers";
33
import {
4+
getDefaultRequestOptions,
45
getNewPage,
56
screenshotDatasetView,
67
type ScreenshotTestContext,
@@ -10,6 +11,9 @@ import {
1011
} from "./dataset_rendering_helpers";
1112
import { encodeUrlHash } from "viewer/controller/url_manager";
1213
import { describe, it, beforeEach, afterEach, expect } from "vitest";
14+
import { updateDatasetDefaultConfiguration } from "admin/rest_api";
15+
import type { DatasetConfiguration } from "viewer/store";
16+
import type { BLEND_MODES } from "viewer/constants";
1317

1418
process.on("unhandledRejection", (err, promise) => {
1519
console.error("Unhandled rejection (promise: ", promise, ", reason: ", err, ").");
@@ -42,7 +46,59 @@ const viewOverrides: Record<string, string> = {
4246
),
4347
};
4448

45-
describe("WebKnossos.org Dataset Rendering", () => {
49+
// The following configuration will be set during the test.
50+
// The tested dataset is *the* example dataset for WK which users
51+
// are guided to from the landing page. We want to ensure good defaults
52+
// and in case they were changed accidentally, it is good that they are
53+
// reset automatically each night.
54+
// If we want different defaults, they should be set here.
55+
const datasetConfiguration: DatasetConfiguration = {
56+
layers: {
57+
color: {
58+
alpha: 100,
59+
gammaCorrectionValue: 1,
60+
min: 0,
61+
color: [255, 255, 255],
62+
max: 255,
63+
isInverted: false,
64+
isInEditMode: false,
65+
isDisabled: false,
66+
intensityRange: [74, 165],
67+
},
68+
predictions: {
69+
alpha: 50,
70+
gammaCorrectionValue: 1,
71+
min: 0,
72+
color: [255, 255, 255],
73+
max: 255,
74+
isInverted: false,
75+
isInEditMode: false,
76+
isDisabled: true,
77+
intensityRange: [103, 199],
78+
},
79+
segmentation: {
80+
alpha: 20,
81+
gammaCorrectionValue: 1,
82+
mapping: null,
83+
color: [255, 255, 255],
84+
isInverted: false,
85+
isInEditMode: false,
86+
isDisabled: false,
87+
},
88+
},
89+
fourBit: false,
90+
interpolation: false,
91+
segmentationPatternOpacity: 25,
92+
colorLayerOrder: ["color", "predictions"],
93+
position: [2924, 4474, 1770],
94+
zoom: 0.9,
95+
blendMode: "Additive" as BLEND_MODES,
96+
nativelyRenderedLayerName: null,
97+
loadingStrategy: "PROGRESSIVE_QUALITY",
98+
renderMissingDataBlack: true,
99+
};
100+
101+
describe("webknossos.org Dataset Rendering", () => {
46102
beforeEach<ScreenshotTestContext>(async (context) => {
47103
await setupBeforeEach(context);
48104
});
@@ -51,42 +107,47 @@ describe("WebKnossos.org Dataset Rendering", () => {
51107
await setupAfterEach(context);
52108
});
53109

54-
it.sequential<ScreenshotTestContext>(
55-
`should render dataset ${demoDatasetName} correctly`,
56-
async ({ browser }) => {
57-
await withRetry(
58-
3,
59-
async () => {
60-
const response = await fetch(
61-
`${URL}/api/datasets/disambiguate/${owningOrganization}/${demoDatasetName}/toId`,
62-
);
63-
const { id: datasetId } = await response.json();
110+
it<ScreenshotTestContext>(`should render dataset ${demoDatasetName} correctly`, async ({
111+
browser,
112+
}) => {
113+
await withRetry(
114+
3,
115+
async () => {
116+
const response = await fetch(
117+
`${URL}/api/datasets/disambiguate/${owningOrganization}/${demoDatasetName}/toId`,
118+
);
119+
const { id: datasetId } = await response.json();
64120

65-
const page = await getNewPage(browser);
66-
const { screenshot, width, height } = await screenshotDatasetView(
67-
page,
68-
URL,
69-
datasetId,
70-
viewOverrides[demoDatasetName],
71-
);
72-
const changedPixels = await compareScreenshot(
73-
screenshot,
74-
width,
75-
height,
76-
SCREENSHOTS_BASE_PATH,
77-
demoDatasetName,
78-
);
79-
await page.close();
121+
await updateDatasetDefaultConfiguration(
122+
datasetId,
123+
datasetConfiguration,
124+
getDefaultRequestOptions(URL),
125+
);
80126

81-
return isPixelEquivalent(changedPixels, width, height);
82-
},
83-
(condition) => {
84-
expect(
85-
condition,
86-
`Dataset with name: "${demoDatasetName}" does not look the same, see ${demoDatasetName}.diff.png for the difference and ${demoDatasetName}.new.png for the new screenshot.`,
87-
).toBe(true);
88-
},
89-
);
90-
},
91-
);
127+
const page = await getNewPage(browser);
128+
const { screenshot, width, height } = await screenshotDatasetView(
129+
page,
130+
URL,
131+
datasetId,
132+
viewOverrides[demoDatasetName],
133+
);
134+
const changedPixels = await compareScreenshot(
135+
screenshot,
136+
width,
137+
height,
138+
SCREENSHOTS_BASE_PATH,
139+
demoDatasetName,
140+
);
141+
await page.close();
142+
143+
return isPixelEquivalent(changedPixels, width, height);
144+
},
145+
(condition) => {
146+
expect(
147+
condition,
148+
`Dataset with name: "${demoDatasetName}" does not look the same, see ${demoDatasetName}.diff.png for the difference and ${demoDatasetName}.new.png for the new screenshot.`,
149+
).toBe(true);
150+
},
151+
);
152+
});
92153
});

frontend/javascripts/test/puppeteer/dataset_rendering_helpers.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,20 @@ async function waitForTracingViewLoad(page: Page) {
243243
console.log("Waiting suspiciously long for page to load...");
244244
}
245245
await sleep(500);
246-
inputCatchers = await page.$(".inputcatcher");
246+
const inputCatcherPromise = page
247+
.$(".inputcatcher")
248+
.then((elements) => ({ type: "inputcatchers", elements }));
249+
const errorPromise = page
250+
.$(".initialization-error-message")
251+
.then((elements) => ({ type: "error", elements }));
252+
253+
const raceResult = await Promise.race([inputCatcherPromise, errorPromise]);
254+
255+
if (raceResult.type === "error") {
256+
throw new Error("Initialization error detected");
257+
}
258+
259+
inputCatchers = raceResult.elements;
247260
}
248261
}
249262

@@ -466,7 +479,7 @@ export async function setupBeforeEach(context: ScreenshotTestContext) {
466479
browser: "chrome",
467480
browser_version: "latest",
468481
os: "os x",
469-
os_version: "mojave",
482+
os_version: "sequoia",
470483
name: context.task.name, // add test name to BrowserStack session
471484
"browserstack.username": process.env.BROWSERSTACK_USERNAME,
472485
"browserstack.accessKey": process.env.BROWSERSTACK_ACCESS_KEY,
Loading
Loading
Loading
Loading

frontend/javascripts/viewer/model/sagas/clip_histogram_saga.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ async function getClippingValues(
6767

6868
const localHist = new Map();
6969
for (let i = 0; i < dataForAllViewPorts.length; i++) {
70-
if (dataForAllViewPorts[i] !== 0) {
71-
const value = localHist.get(dataForAllViewPorts[i]);
72-
localHist.set(dataForAllViewPorts[i], value != null ? value + 1 : 1);
70+
const value = dataForAllViewPorts[i];
71+
// Don't use Number.abs because value can be BigInt
72+
if (value !== 0 && value !== Number.POSITIVE_INFINITY && value !== Number.NEGATIVE_INFINITY) {
73+
const counter = localHist.get(dataForAllViewPorts[i]);
74+
localHist.set(dataForAllViewPorts[i], counter != null ? counter + 1 : 1);
7375
}
7476
}
7577

tools/create-changelog-entry.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ def main():
6666
with open(file_path, "w") as f:
6767
f.write(TEMPLATE)
6868

69-
print(f"✅ Created changelog entry: {file_path}")
69+
print(
70+
f"✅ Created new file for a changelog entry. Please adapt its content here: {file_path}"
71+
)
7072

7173

7274
if __name__ == "__main__":

unreleased_changes/8702.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Fixed
2+
- Fixed the sandbox mode for annotations.
3+
- Fixed the nightly screenshot tests.

util/src/main/scala/com/scalableminds/util/objectid/ObjectId.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ object ObjectId extends FoxImplicits {
2020
parseCommaSeparated(idsStrOpt)(fromString)
2121
private def fromBsonId(bson: BSONObjectID) = ObjectId(bson.stringify)
2222
def fromStringSync(input: String): Option[ObjectId] = BSONObjectID.parse(input).map(fromBsonId).toOption
23-
def dummyId: ObjectId = ObjectId("dummyObjectId")
23+
def dummyId: ObjectId = ObjectId("000000000000000000000000")
2424

2525
implicit object ObjectIdFormat extends Format[ObjectId] {
2626
override def reads(json: JsValue): JsResult[ObjectId] =

0 commit comments

Comments
 (0)