From f50011a1c9efaa134a3710d62606eb1a9d13d051 Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 17 Mar 2026 10:47:16 +0100 Subject: [PATCH 1/9] Finalize allowlisting testfiles and unit test --- .../allowlisting/allowlisted_proxyqto.ifc | 57 ++++++ .../allowlisting/allowlisted_storeyqto.ifc | 49 +++++ .../allowlisted_transformertype.ifc} | 8 +- .../not_allowlisted_storeyqto.ifc | 49 +++++ .../not_allowlisted_transformertype.ifc} | 4 + e2e/tests/allowlisting.test.js | 170 ++++++++++++++++++ 6 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 e2e/fixtures/allowlisting/allowlisted_proxyqto.ifc create mode 100644 e2e/fixtures/allowlisting/allowlisted_storeyqto.ifc rename e2e/fixtures/{transformer_type_whitelisted.ifc => allowlisting/allowlisted_transformertype.ifc} (72%) create mode 100644 e2e/fixtures/allowlisting/not_allowlisted_storeyqto.ifc rename e2e/fixtures/{transformer_type_not_whitelisted.ifc => allowlisting/not_allowlisted_transformertype.ifc} (82%) create mode 100644 e2e/tests/allowlisting.test.js diff --git a/e2e/fixtures/allowlisting/allowlisted_proxyqto.ifc b/e2e/fixtures/allowlisting/allowlisted_proxyqto.ifc new file mode 100644 index 00000000..9b57d432 --- /dev/null +++ b/e2e/fixtures/allowlisting/allowlisted_proxyqto.ifc @@ -0,0 +1,57 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2026-02-24T09:56:49',(''),(''),'IfcOpenShell 0.8.0','IfcOpenShell 0.8.0',''); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPERSON($,'User','Generated',$,$,$,$,$); +#2=IFCORGANIZATION($,'Example Org',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'1.0','IfcOpenShell','ifcopenshell'); +#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,$,$,1771923409); +#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#7=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#8=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#9=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#10=IFCUNITASSIGNMENT((#6,#7,#8,#9)); +#11=IFCCARTESIANPOINT((0.,0.,0.)); +#12=IFCDIRECTION((0.,0.,1.)); +#13=IFCDIRECTION((1.,0.,0.)); +#14=IFCAXIS2PLACEMENT3D(#11,#12,#13); +#15=IFCGEOMETRICREPRESENTATIONCONTEXT('Model','Model',3,1.E-05,#14,$); +#16=IFCPROJECT('1BMjysx0n9MvGMpzkFND_A',#5,'Example Project',$,$,$,$,(#15),#10); +#17=IFCCARTESIANPOINT((0.,0.,0.)); +#18=IFCDIRECTION((0.,0.,1.)); +#19=IFCDIRECTION((1.,0.,0.)); +#20=IFCAXIS2PLACEMENT3D(#17,#18,#19); +#21=IFCLOCALPLACEMENT($,#20); +#22=IFCSITE('1Fkx8DCKTFwvnU9ad7fr64',#5,'Site',$,$,#21,$,$,.ELEMENT.,$,$,$,$,$); +#23=IFCCARTESIANPOINT((0.,0.,0.)); +#24=IFCDIRECTION((0.,0.,1.)); +#25=IFCDIRECTION((1.,0.,0.)); +#26=IFCAXIS2PLACEMENT3D(#23,#24,#25); +#27=IFCLOCALPLACEMENT(#21,#26); +#28=IFCBUILDING('3MvFJc7E1C_PJg__ZCQSxq',#5,'Building',$,$,#27,$,$,.ELEMENT.,$,$,$); +#29=IFCCARTESIANPOINT((0.,0.,0.)); +#30=IFCDIRECTION((0.,0.,1.)); +#31=IFCDIRECTION((1.,0.,0.)); +#32=IFCAXIS2PLACEMENT3D(#29,#30,#31); +#33=IFCLOCALPLACEMENT(#27,#32); +#34=IFCBUILDINGSTOREY('283AuHtNn9XA4199h9ReZ0',#5,'Level 0',$,$,#33,$,$,.ELEMENT.,0.); +#35=IFCRELAGGREGATES('36lNwFyi5BIfxze4QqIrl3',#5,$,$,#16,(#22)); +#36=IFCRELAGGREGATES('25bFosZlv5R8R32MNvf9$Y',#5,$,$,#22,(#28)); +#37=IFCRELAGGREGATES('3Mf03PrbD6CfEDjAk6NWrg',#5,$,$,#28,(#34)); +#38=IFCCARTESIANPOINT((0.,0.,0.)); +#39=IFCDIRECTION((0.,0.,1.)); +#40=IFCDIRECTION((1.,0.,0.)); +#41=IFCAXIS2PLACEMENT3D(#38,#39,#40); +#42=IFCLOCALPLACEMENT(#33,#41); +#43=IFCBUILDINGELEMENTPROXY('3FQgpTOKHC6PgbGLrO7C3X',#5,'Proxy 01',$,$,#42,$,$,.NOTDEFINED.); +#44=IFCRELCONTAINEDINSPATIALSTRUCTURE('0jsrh1zyP8hRK5zq7BW7Jz',#5,$,$,(#43),#34); +#45=IFCQUANTITYAREA('NetSurfaceArea',$,$,12.34,$); +#46=IFCQUANTITYVOLUME('NetVolume',$,$,56.78,$); +#47=IFCELEMENTQUANTITY('2ETxYtAXDDVBdYAZjPNhxl',$,'Qto_BuildingElementProxyQuantities',$,'BaseQuantities',(#45,#46)); +#48=IFCRELDEFINESBYPROPERTIES('1RiLQ$p2jD2BR9e6LxFE2r',#5,$,$,(#43),#47); +ENDSEC; +END-ISO-10303-21; diff --git a/e2e/fixtures/allowlisting/allowlisted_storeyqto.ifc b/e2e/fixtures/allowlisting/allowlisted_storeyqto.ifc new file mode 100644 index 00000000..9d8910b0 --- /dev/null +++ b/e2e/fixtures/allowlisting/allowlisted_storeyqto.ifc @@ -0,0 +1,49 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2026-02-24T09:56:49',(''),(''),'IfcOpenShell 0.8.0','IfcOpenShell 0.8.0',''); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPERSON($,'User','Generated',$,$,$,$,$); +#2=IFCORGANIZATION($,'Example Org',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'1.0','IfcOpenShell','ifcopenshell'); +#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,$,$,1771923409); +#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#7=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#8=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#9=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#10=IFCUNITASSIGNMENT((#6,#7,#8,#9)); +#11=IFCCARTESIANPOINT((0.,0.,0.)); +#12=IFCDIRECTION((0.,0.,1.)); +#13=IFCDIRECTION((1.,0.,0.)); +#14=IFCAXIS2PLACEMENT3D(#11,#12,#13); +#15=IFCGEOMETRICREPRESENTATIONCONTEXT('Model','Model',3,1.E-05,#14,$); +#16=IFCPROJECT('1iyfGnLr1A5QObe35JynRA',#5,'Example Project',$,$,$,$,(#15),#10); +#17=IFCCARTESIANPOINT((0.,0.,0.)); +#18=IFCDIRECTION((0.,0.,1.)); +#19=IFCDIRECTION((1.,0.,0.)); +#20=IFCAXIS2PLACEMENT3D(#17,#18,#19); +#21=IFCLOCALPLACEMENT($,#20); +#22=IFCSITE('0xq5s0Sef4eBW2L6xclQoY',#5,'Site',$,$,#21,$,$,.ELEMENT.,$,$,$,$,$); +#23=IFCCARTESIANPOINT((0.,0.,0.)); +#24=IFCDIRECTION((0.,0.,1.)); +#25=IFCDIRECTION((1.,0.,0.)); +#26=IFCAXIS2PLACEMENT3D(#23,#24,#25); +#27=IFCLOCALPLACEMENT(#21,#26); +#28=IFCBUILDING('19vP5ojgzA4Ak8tpTDmsFk',#5,'Building',$,$,#27,$,$,.ELEMENT.,$,$,$); +#29=IFCCARTESIANPOINT((0.,0.,0.)); +#30=IFCDIRECTION((0.,0.,1.)); +#31=IFCDIRECTION((1.,0.,0.)); +#32=IFCAXIS2PLACEMENT3D(#29,#30,#31); +#33=IFCLOCALPLACEMENT(#27,#32); +#34=IFCBUILDINGSTOREY('0udBjQTrz6qesrXheLTe9j',#5,'Level 0',$,$,#33,$,$,.ELEMENT.,0.); +#35=IFCRELAGGREGATES('168q3RcXXBpw6F0BU2RPbH',#5,$,$,#16,(#22)); +#36=IFCRELAGGREGATES('0nFUVKgJ11avoRp_pS0krY',#5,$,$,#22,(#28)); +#37=IFCRELAGGREGATES('3XdgFhFOPFaf0jfpU9_z85',#5,$,$,#28,(#34)); +#38=IFCQUANTITYLENGTH('NetHeight',$,$,12.34,$); +#39=IFCELEMENTQUANTITY('3KXvj87n5EZR22_nDKi31k',$,'Qto_BuildingStoreyBaseQuantities',$,'BaseQuantities',(#38)); +#40=IFCRELDEFINESBYPROPERTIES('1ihyW38pfDy8xBvjsE5Dq8',#5,$,$,(#34),#39); +ENDSEC; +END-ISO-10303-21; diff --git a/e2e/fixtures/transformer_type_whitelisted.ifc b/e2e/fixtures/allowlisting/allowlisted_transformertype.ifc similarity index 72% rename from e2e/fixtures/transformer_type_whitelisted.ifc rename to e2e/fixtures/allowlisting/allowlisted_transformertype.ifc index 1a8d4cb2..93cc08d2 100644 --- a/e2e/fixtures/transformer_type_whitelisted.ifc +++ b/e2e/fixtures/allowlisting/allowlisted_transformertype.ifc @@ -1,14 +1,14 @@ ISO-10303-21; HEADER; FILE_DESCRIPTION(('ViewDefinition [ReferenceView_V1.2]'),'2;1'); -FILE_NAME('','2026-01-30T19:23:32',(''),(''),'IfcOpenShell contributors - IfcOpenShell - v0.8.0','IfcOpenShell contributors - IfcOpenShell - v0.8.0',''); +FILE_NAME('','2026-01-30T19:23:32',(''),(''),'Test3 - Test3 - v3.0','Test3 - Test3 - v3.0',''); FILE_SCHEMA(('IFC4')); ENDSEC; DATA; #1=IFCPERSON($,$,'',$,$,$,$,$); #2=IFCORGANIZATION($,'',$,$,$); #3=IFCPERSONANDORGANIZATION(#1,#2,$); -#4=IFCAPPLICATION(#2,'0.8.0','IfcOpenShell contributors - IfcOpenShell - v0.8.0',''); +#4=IFCAPPLICATION(#2,'0.8.0','Test3 - Test3 - v3.0',''); #5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,#3,#4,1769801012); #6=IFCDIRECTION((1.,0.,0.)); #7=IFCDIRECTION((0.,0.,1.)); @@ -28,5 +28,9 @@ DATA; #21=IFCTRANSFORMER('152EkAn$n33hZ6u_M0ABgY',$,$,$,$,$,$,$,$); #22=IFCTRANSFORMERTYPE('1_GJgCelfA8uThkk8mMvX9',$,'T',$,$,$,$,$,$,.CURRENT.); #23=IFCRELDEFINESBYTYPE('0_sGsYHQLAMgWs1UcXRuBz',$,$,$,(#21),#22); +#25=IFCRELAGGREGATES('1_FiI$EtrAVumQxKPOEZu6',#5,$,$,#20,(#41)); +#41=IFCSITE('060XCG67v9ZO7i9CQZAKvK',#5,$,$,$,$,$,$,.ELEMENT.,$,$,0.,$,$); +#24=IFCRELCONTAINEDINSPATIALSTRUCTURE('0mSqFiWyj4IPh$N3UgmOC9',#5,$,$,(#21),#41); +#103= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#11,$,.MODEL_VIEW.,$); ENDSEC; END-ISO-10303-21; diff --git a/e2e/fixtures/allowlisting/not_allowlisted_storeyqto.ifc b/e2e/fixtures/allowlisting/not_allowlisted_storeyqto.ifc new file mode 100644 index 00000000..a65acc2b --- /dev/null +++ b/e2e/fixtures/allowlisting/not_allowlisted_storeyqto.ifc @@ -0,0 +1,49 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('ViewDefinition [CoordinationView]'),'2;1'); +FILE_NAME('','2026-02-24T09:56:49',(''),(''),'IfcOpenShell 0.8.0','IfcOpenShell 0.8.0',''); +FILE_SCHEMA(('IFC4')); +ENDSEC; +DATA; +#1=IFCPERSON($,'User','Generated',$,$,$,$,$); +#2=IFCORGANIZATION($,'Example Org',$,$,$); +#3=IFCPERSONANDORGANIZATION(#1,#2,$); +#4=IFCAPPLICATION(#2,'1.0','IfcOpenShell','ifcopenshell'); +#5=IFCOWNERHISTORY(#3,#4,$,.NOTDEFINED.,$,$,$,1771923409); +#6=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); +#7=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); +#8=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); +#9=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); +#10=IFCUNITASSIGNMENT((#6,#7,#8,#9)); +#11=IFCCARTESIANPOINT((0.,0.,0.)); +#12=IFCDIRECTION((0.,0.,1.)); +#13=IFCDIRECTION((1.,0.,0.)); +#14=IFCAXIS2PLACEMENT3D(#11,#12,#13); +#15=IFCGEOMETRICREPRESENTATIONCONTEXT('Model','Model',3,1.E-05,#14,$); +#16=IFCPROJECT('1iyfGnLr1A5QObe35JynRA',#5,'Example Project',$,$,$,$,(#15),#10); +#17=IFCCARTESIANPOINT((0.,0.,0.)); +#18=IFCDIRECTION((0.,0.,1.)); +#19=IFCDIRECTION((1.,0.,0.)); +#20=IFCAXIS2PLACEMENT3D(#17,#18,#19); +#21=IFCLOCALPLACEMENT($,#20); +#22=IFCSITE('0xq5s0Sef4eBW2L6xclQoY',#5,'Site',$,$,#21,$,$,.ELEMENT.,$,$,$,$,$); +#23=IFCCARTESIANPOINT((0.,0.,0.)); +#24=IFCDIRECTION((0.,0.,1.)); +#25=IFCDIRECTION((1.,0.,0.)); +#26=IFCAXIS2PLACEMENT3D(#23,#24,#25); +#27=IFCLOCALPLACEMENT(#21,#26); +#28=IFCBUILDING('19vP5ojgzA4Ak8tpTDmsFk',#5,'Building',$,$,#27,$,$,.ELEMENT.,$,$,$); +#29=IFCCARTESIANPOINT((0.,0.,0.)); +#30=IFCDIRECTION((0.,0.,1.)); +#31=IFCDIRECTION((1.,0.,0.)); +#32=IFCAXIS2PLACEMENT3D(#29,#30,#31); +#33=IFCLOCALPLACEMENT(#27,#32); +#34=IFCBUILDINGSTOREY('0udBjQTrz6qesrXheLTe9j',#5,'Level 0',$,$,#33,$,$,.ELEMENT.,0.); +#35=IFCRELAGGREGATES('168q3RcXXBpw6F0BU2RPbH',#5,$,$,#16,(#22)); +#36=IFCRELAGGREGATES('0nFUVKgJ11avoRp_pS0krY',#5,$,$,#22,(#28)); +#37=IFCRELAGGREGATES('3XdgFhFOPFaf0jfpU9_z85',#5,$,$,#28,(#34)); +#38=IFCQUANTITYLENGTH('NtHeight',$,$,12.34,$); +#39=IFCELEMENTQUANTITY('3KXvj87n5EZR22_nDKi31k',$,'Qto_BuildingStoreyBaseQuantities',$,'BaseQuantities',(#38)); +#40=IFCRELDEFINESBYPROPERTIES('1ihyW38pfDy8xBvjsE5Dq8',#5,$,$,(#34),#39); +ENDSEC; +END-ISO-10303-21; diff --git a/e2e/fixtures/transformer_type_not_whitelisted.ifc b/e2e/fixtures/allowlisting/not_allowlisted_transformertype.ifc similarity index 82% rename from e2e/fixtures/transformer_type_not_whitelisted.ifc rename to e2e/fixtures/allowlisting/not_allowlisted_transformertype.ifc index ce05f1a4..4ae9cb1d 100644 --- a/e2e/fixtures/transformer_type_not_whitelisted.ifc +++ b/e2e/fixtures/allowlisting/not_allowlisted_transformertype.ifc @@ -28,5 +28,9 @@ DATA; #21=IFCTRANSFORMER('152EkAn$n33hZ6u_M0ABgY',$,$,$,$,$,$,$,$); #23=IFCRELDEFINESBYTYPE('0_sGsYHQLAMgWs1UcXRuBz',$,$,$,(#21),#24); #24=IFCWALLTYPE('2so5A4Lx95i9ZO0y$h3Upk',$,'T',$,$,$,$,$,$,.MOVABLE.); +#25=IFCRELAGGREGATES('1_FiI$EtrAVumQxKPOEZu6',#5,$,$,#20,(#41)); +#41=IFCSITE('060XCG67v9ZO7i9CQZAKvK',#5,$,$,$,$,$,$,.ELEMENT.,$,$,0.,$,$); +#26=IFCRELCONTAINEDINSPATIALSTRUCTURE('0mSqFiWyj4IPh$N3UgmOC9',#5,$,$,(#21),#41); +#103= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#11,$,.MODEL_VIEW.,$); ENDSEC; END-ISO-10303-21; diff --git a/e2e/tests/allowlisting.test.js b/e2e/tests/allowlisting.test.js new file mode 100644 index 00000000..889881b4 --- /dev/null +++ b/e2e/tests/allowlisting.test.js @@ -0,0 +1,170 @@ +import { test, expect } from '@playwright/test'; +import { readFileSync } from 'fs'; +import { basename, extname, resolve } from 'path'; + +const BASE_URL = 'http://localhost:3000'; +const BFF_URL = 'http://localhost:8000/bff'; + +const STATUS_COLUMNS = { + schema: 3, + rules: 4, +}; + +const STATUS_ICONS = { + v: 'CheckCircleIcon', + i: 'ErrorIcon', +}; + +const ALLOWLISTING_CASES = [ + { + fixturePath: 'fixtures/allowlisting/allowlisted_proxyqto.ifc', + expectedSchema: 'v', + expectedRules: 'v', + }, + { + fixturePath: 'fixtures/allowlisting/allowlisted_storeyqto.ifc', + expectedSchema: 'v', + expectedRules: 'v', + }, + { + fixturePath: 'fixtures/allowlisting/not_allowlisted_storeyqto.ifc', + expectedSchema: 'v', + expectedRules: 'i', + }, + { + fixturePath: 'fixtures/allowlisting/allowlisted_transformertype.ifc', + expectedSchema: 'v', + expectedRules: 'v', + }, + { + fixturePath: 'fixtures/allowlisting/not_allowlisted_transformertype.ifc', + expectedSchema: 'i', + expectedRules: 'v', + }, +]; + +let createdModelIds = []; + +function createUploadPayload(fixturePath) { + const absolutePath = resolve(process.cwd(), fixturePath); + const originalName = basename(fixturePath); + const extension = extname(originalName); + const stem = originalName.slice(0, -extension.length); + const uniqueSuffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + + return { + buffer: readFileSync(absolutePath), + uploadName: `${stem}-${uniqueSuffix}${extension}`, + }; +} + +async function gotoDashboard(page) { + await page.goto(`${BASE_URL}/dashboard`); + await expect(page.getByRole('columnheader', { name: 'File Name' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Upload & Validate' })).toBeVisible(); + await expect(page.locator('input[type="file"]').first()).toBeAttached(); +} + +async function uploadFixture(page, fixturePath) { + const { buffer, uploadName } = createUploadPayload(fixturePath); + + await gotoDashboard(page); + await page.locator('input[type="file"]').first().setInputFiles({ + name: uploadName, + mimeType: 'application/octet-stream', + buffer, + }); + await page.getByRole('button', { name: 'Upload & Validate' }).click(); + + const row = page.locator('tbody tr').filter({ hasText: uploadName }).first(); + await expect(row).toBeVisible({ timeout: 60_000 }); + + return uploadName; +} + +async function getModelByFilename(page, fileName) { + const response = await page.request.get(`${BFF_URL}/api/models_paginated/0/25`); + expect(response.ok()).toBeTruthy(); + + const data = await response.json(); + return data.models.find((model) => model.filename === fileName) ?? null; +} + +async function waitForCompletedModel(page, fileName) { + let latestModel = null; + + await expect + .poll( + async () => { + latestModel = await getModelByFilename(page, fileName); + return latestModel?.progress ?? 'missing'; + }, + { + timeout: 180_000, + intervals: [1_000, 2_000, 5_000], + } + ) + .toBe(100); + + return latestModel; +} + +async function expectStatusIcon(row, columnKey, status) { + const iconTestId = STATUS_ICONS[status]; + const cell = row.locator('td').nth(STATUS_COLUMNS[columnKey]); + + await expect(cell.locator(`[data-testid="${iconTestId}"]`)).toBeVisible(); +} + +async function deleteCreatedModels(page) { + if (createdModelIds.length === 0) { + return; + } + + const cookies = await page.context().cookies(); + const csrfToken = cookies.find((cookie) => cookie.name === 'csrftoken')?.value; + + if (!csrfToken) { + createdModelIds = []; + return; + } + + const response = await page.request.delete(`${BFF_URL}/api/delete/${createdModelIds.join(',')}`, { + headers: { + 'x-csrf-token': csrfToken, + }, + }); + + expect(response.ok()).toBeTruthy(); + createdModelIds = []; +} + +test.describe('UI - Allowlisting dashboard statuses', () => { + test.describe.configure({ mode: 'serial' }); + + test.afterEach(async ({ page }) => { + await deleteCreatedModels(page); + }); + + for (const allowlistingCase of ALLOWLISTING_CASES) { + test(`shows the expected schema and rules status for ${basename(allowlistingCase.fixturePath)}`, async ({ page }) => { + test.slow(); + + const uploadedFileName = await uploadFixture(page, allowlistingCase.fixturePath); + const model = await waitForCompletedModel(page, uploadedFileName); + + expect(model).not.toBeNull(); + createdModelIds.push(model.id); + + expect(model.status_schema).toBe(allowlistingCase.expectedSchema); + expect(model.status_rules).toBe(allowlistingCase.expectedRules); + + await gotoDashboard(page); + + const row = page.locator('tbody tr').filter({ hasText: uploadedFileName }).first(); + await expect(row).toBeVisible(); + await expectStatusIcon(row, 'schema', allowlistingCase.expectedSchema); + await expectStatusIcon(row, 'rules', allowlistingCase.expectedRules); + }); + } +}); From a4fd13e3a52ed707f78d694696a765428d0362ff Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 17 Mar 2026 10:47:41 +0100 Subject: [PATCH 2/9] Apply allowlisting UI change to schema result as well --- frontend/src/SchemaResult.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/SchemaResult.js b/frontend/src/SchemaResult.js index 9df81dca..0f30d534 100644 --- a/frontend/src/SchemaResult.js +++ b/frontend/src/SchemaResult.js @@ -12,6 +12,7 @@ import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TablePagination from '@mui/material/TablePagination'; import { statusToColor, severityToColor, severityToLabel, statusToLabel } from './mappings'; +import { Link } from "react-router-dom"; function coerceToStr(v) { if (!v) { @@ -158,7 +159,10 @@ export default function SchemaResult({ summary, count, content, status, instance {instances[row.instance_id] ? instances[row.instance_id].guid : '-'} {instances[row.instance_id] ? instances[row.instance_id].type : '-'} - {severityToLabel[row.severity]} + {row.allowlisted + ? ⓘ Allowlisted + : {severityToLabel[row.severity]} + } {featureDescription && row.expected && row.observed From e6a5e719cfc12e73f9f7c35e6ea14200afb519d6 Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Wed, 18 Mar 2026 19:36:01 +0100 Subject: [PATCH 3/9] handle celery error --- .github/workflows/playwright.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index f6197365..73ce9dd6 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -27,8 +27,8 @@ jobs: MEDIA_ROOT: /tmp/files_storage CELERY_BROKER_URL: redis://redis:6379/0 CELERY_RESULT_BACKEND: django-db - CELERY_TASK_ALWAYS_EAGER: "false" - CELERY_TASK_EAGER_PROPAGATES: "false" + CELERY_TASK_ALWAYS_EAGER: "true" + CELERY_TASK_EAGER_PROPAGATES: "true" DJANGO_ALLOWED_HOSTS: "localhost 127.0.0.1" DJANGO_TRUSTED_ORIGINS: "http://localhost:3000 http://127.0.0.1:3000 http://localhost:8000 http://127.0.0.1:8000" POSTGRES_USER: postgres @@ -104,7 +104,7 @@ jobs: - name: Download and install ifcopenshell run: | - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-a51b2c5-linux64.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-1c5b825-linux64.zip" mkdir -p .dev/venv/lib/python3.11/site-packages unzip -o -d .dev/venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip rm /tmp/ifcopenshell_python.zip From f52ad7f285a8866224ea4200f0833dbb805031a5 Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Thu, 19 Mar 2026 00:02:59 +0100 Subject: [PATCH 4/9] ifcopenshell update for macos & playwright checks --- .github/workflows/playwright.yml | 2 +- backend/Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 73ce9dd6..ffde9b50 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -104,7 +104,7 @@ jobs: - name: Download and install ifcopenshell run: | - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-1c5b825-linux64.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-51b2c5-linux64.zip" mkdir -p .dev/venv/lib/python3.11/site-packages unzip -o -d .dev/venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip rm /tmp/ifcopenshell_python.zip diff --git a/backend/Makefile b/backend/Makefile index 08ab1ae0..11d88523 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -22,7 +22,7 @@ install: venv install-macos: venv find . -name 'requirements.txt' -exec $(PIP) install -r {} \; $(PIP) install -r requirements.txt - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-1c5b825-macos64.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-51b2c5-macos64.zip" mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages unzip /tmp/ifcopenshell_python.zip -d $(VIRTUAL_ENV)/lib/python3.11/site-packages rm /tmp/ifcopenshell_python.zip @@ -30,7 +30,7 @@ install-macos: venv install-macos-m1: venv find . -name 'requirements.txt' -exec $(PIP) install -r {} \; $(PIP) install -r requirements.txt - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-1c5b825-macosm164.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-51b2c5-macosm164.zip" mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages unzip /tmp/ifcopenshell_python.zip -d $(VIRTUAL_ENV)/lib/python3.11/site-packages rm /tmp/ifcopenshell_python.zip From 7e96a69ba7d006f674c8b350b3cc3650495b44b4 Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Thu, 19 Mar 2026 12:27:54 +0100 Subject: [PATCH 5/9] correct ifcopenshell checkout --- .github/workflows/playwright.yml | 2 +- backend/Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index ffde9b50..82f5b4d4 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -104,7 +104,7 @@ jobs: - name: Download and install ifcopenshell run: | - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-51b2c5-linux64.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-a51b2c5-linux64.zip" mkdir -p .dev/venv/lib/python3.11/site-packages unzip -o -d .dev/venv/lib/python3.11/site-packages /tmp/ifcopenshell_python.zip rm /tmp/ifcopenshell_python.zip diff --git a/backend/Makefile b/backend/Makefile index 11d88523..bae726c4 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -22,7 +22,7 @@ install: venv install-macos: venv find . -name 'requirements.txt' -exec $(PIP) install -r {} \; $(PIP) install -r requirements.txt - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-51b2c5-macos64.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-a51b2c5-macos64.zip" mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages unzip /tmp/ifcopenshell_python.zip -d $(VIRTUAL_ENV)/lib/python3.11/site-packages rm /tmp/ifcopenshell_python.zip @@ -30,7 +30,7 @@ install-macos: venv install-macos-m1: venv find . -name 'requirements.txt' -exec $(PIP) install -r {} \; $(PIP) install -r requirements.txt - wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-51b2c5-macosm164.zip" + wget -O /tmp/ifcopenshell_python.zip "https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-python-311-v0.8.5-a51b2c5-macosm164.zip" mkdir -p $(VIRTUAL_ENV)/lib/python3.11/site-packages unzip /tmp/ifcopenshell_python.zip -d $(VIRTUAL_ENV)/lib/python3.11/site-packages rm /tmp/ifcopenshell_python.zip From 43fc280962e2e5ff6a9d822f4cb1c2952c36c95d Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Thu, 19 Mar 2026 12:48:05 +0100 Subject: [PATCH 6/9] add networkidle wait to playwright --- e2e/tests/allowlisting.test.js | 2 +- e2e/tests/validate.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/e2e/tests/allowlisting.test.js b/e2e/tests/allowlisting.test.js index 889881b4..84b74a9e 100644 --- a/e2e/tests/allowlisting.test.js +++ b/e2e/tests/allowlisting.test.js @@ -59,7 +59,7 @@ function createUploadPayload(fixturePath) { } async function gotoDashboard(page) { - await page.goto(`${BASE_URL}/dashboard`); + await page.goto(`${BASE_URL}/dashboard`, { waitUntil: 'networkidle' }); await expect(page.getByRole('columnheader', { name: 'File Name' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Upload & Validate' })).toBeVisible(); await expect(page.locator('input[type="file"]').first()).toBeAttached(); diff --git a/e2e/tests/validate.test.js b/e2e/tests/validate.test.js index 1ab272dd..a2f4c568 100644 --- a/e2e/tests/validate.test.js +++ b/e2e/tests/validate.test.js @@ -8,7 +8,7 @@ test.describe('Validate WebUI Tests', () => { test('should be able to see the homepage', async ({ page }) => { // navigate to the Validate Web UI - await page.goto(BASE_URL); + await page.goto(BASE_URL, { waitUntil: 'networkidle' }); // check if certain elements are visible // a left-hand side menu, a text element and an upload button @@ -20,7 +20,7 @@ test.describe('Validate WebUI Tests', () => { test('should be able to see the dashboard', async ({ page }) => { // navigate to the Validate Web UI - await page.goto(BASE_URL); + await page.goto(BASE_URL, { waitUntil: 'networkidle' }); // click the dashboard link await page.getByRole('link', { name: 'Validation', exact: true }).click(); @@ -38,7 +38,7 @@ test.describe('Validate WebUI Tests', () => { await page.context().clearCookies(); // navigate to the Validate Web UI - await page.goto(BASE_URL); + await page.goto(BASE_URL, { waitUntil: 'networkidle' }); // check for a specific cookie by name; retry with delay if not found let retries = 5; @@ -61,7 +61,7 @@ test.describe('Validate WebUI Tests', () => { await page.context().clearCookies(); // navigate to the Validate Web UI - await page.goto(BASE_URL); + await page.goto(BASE_URL, { waitUntil: 'networkidle' }); // wait for a specific cookie by name; retry with delay if not found let retries = 5; From 664fec321ff3872ad229576873afc12869a0fb0f Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Thu, 19 Mar 2026 12:57:15 +0100 Subject: [PATCH 7/9] add warmup --- e2e/tests/global-setup.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/e2e/tests/global-setup.js b/e2e/tests/global-setup.js index 07dc28b6..5c778259 100644 --- a/e2e/tests/global-setup.js +++ b/e2e/tests/global-setup.js @@ -1,11 +1,23 @@ - -// tests/global-setup.js async function globalSetup(config) { console.log('🚀 Starting global setup for Playwright tests...'); - - // Any global setup logic can go here - // For example, seeding test data, setting up test databases, etc. - + + // Warm up the frontend dev server — it may still be compiling after the health check passes + const baseURL = 'http://localhost:3000'; + const maxRetries = 30; + for (let i = 0; i < maxRetries; i++) { + try { + const res = await fetch(baseURL); + const body = await res.text(); + if (body.includes('
') || body.includes('bundle.js') || res.ok) { + console.log(`✅ Frontend ready after ${i + 1} attempt(s)`); + break; + } + } catch { + // server not ready yet + } + await new Promise(r => setTimeout(r, 2000)); + } + console.log('✅ Global setup completed'); } From d3dc995ab20c98f8191147d042f7211c8efe6a50 Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Thu, 19 Mar 2026 13:15:25 +0100 Subject: [PATCH 8/9] allowlist tests afterwards, change warmup to be sure --- e2e/playwright.config.js | 7 ++++++- e2e/tests/allowlisting.test.js | 2 +- e2e/tests/global-setup.js | 29 +++++++++++++---------------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/e2e/playwright.config.js b/e2e/playwright.config.js index 4f343da4..06b24bf9 100644 --- a/e2e/playwright.config.js +++ b/e2e/playwright.config.js @@ -37,7 +37,12 @@ export default defineConfig({ projects: [ { name: 'e2e-tests', - testMatch: '**/tests/*.test.js', + testMatch: [ + '**/tests/django_admin.test.js', + '**/tests/validate.test.js', + '**/tests/validate_api.test.js', + '**/tests/allowlisting.test.js', + ], use: { ...devices['Desktop Chrome'], // API tests don't need a browser, but we'll use request context diff --git a/e2e/tests/allowlisting.test.js b/e2e/tests/allowlisting.test.js index 84b74a9e..2f54b247 100644 --- a/e2e/tests/allowlisting.test.js +++ b/e2e/tests/allowlisting.test.js @@ -60,7 +60,7 @@ function createUploadPayload(fixturePath) { async function gotoDashboard(page) { await page.goto(`${BASE_URL}/dashboard`, { waitUntil: 'networkidle' }); - await expect(page.getByRole('columnheader', { name: 'File Name' })).toBeVisible(); + await expect(page.getByRole('columnheader', { name: 'File Name' })).toBeVisible({ timeout: 30_000 }); await expect(page.getByRole('button', { name: 'Upload & Validate' })).toBeVisible(); await expect(page.locator('input[type="file"]').first()).toBeAttached(); } diff --git a/e2e/tests/global-setup.js b/e2e/tests/global-setup.js index 5c778259..cd515014 100644 --- a/e2e/tests/global-setup.js +++ b/e2e/tests/global-setup.js @@ -1,22 +1,19 @@ +import { chromium } from '@playwright/test'; + async function globalSetup(config) { - console.log('🚀 Starting global setup for Playwright tests...'); + console.log('🚀 Starting global setup...'); - // Warm up the frontend dev server — it may still be compiling after the health check passes - const baseURL = 'http://localhost:3000'; - const maxRetries = 30; - for (let i = 0; i < maxRetries; i++) { - try { - const res = await fetch(baseURL); - const body = await res.text(); - if (body.includes('
') || body.includes('bundle.js') || res.ok) { - console.log(`✅ Frontend ready after ${i + 1} attempt(s)`); - break; - } - } catch { - // server not ready yet - } - await new Promise(r => setTimeout(r, 2000)); + // Warm up the frontend by loading it in a real browser + // This triggers JS bundle compilation on the dev server + const browser = await chromium.launch(); + const page = await browser.newPage(); + try { + await page.goto('http://localhost:3000', { waitUntil: 'networkidle', timeout: 60_000 }); + console.log('✅ Frontend warmed up'); + } catch (e) { + console.warn('⚠️ Frontend warmup timed out:', e.message); } + await browser.close(); console.log('✅ Global setup completed'); } From 77d482bdffc47ddb9fcd20835edc05c59e504106 Mon Sep 17 00:00:00 2001 From: Ghesselink Date: Thu, 19 Mar 2026 15:58:34 +0100 Subject: [PATCH 9/9] run tasks sequentially; more generous timeout --- .github/workflows/playwright.yml | 2 +- e2e/playwright.config.js | 2 +- e2e/tests/allowlisting.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 82f5b4d4..be668b39 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -169,7 +169,7 @@ jobs: VIRTUAL_ENV: backend/.dev/venv - name: Run Playwright tests - run: npx playwright test --workers="$(node -p "Math.max(1, Math.min(Math.floor((require('os').cpus()?.length||2)*0.75), 8))")" + run: npx playwright test working-directory: ./e2e env: DJANGO_DB: postgresql diff --git a/e2e/playwright.config.js b/e2e/playwright.config.js index 06b24bf9..530bf69d 100644 --- a/e2e/playwright.config.js +++ b/e2e/playwright.config.js @@ -78,7 +78,7 @@ export default defineConfig({ /* Test timeout */ timeout: 60 * 1000, expect: { - timeout: 15 * 1000, + timeout: 30 * 1000, }, /* Output directory for test results */ diff --git a/e2e/tests/allowlisting.test.js b/e2e/tests/allowlisting.test.js index 2f54b247..84b74a9e 100644 --- a/e2e/tests/allowlisting.test.js +++ b/e2e/tests/allowlisting.test.js @@ -60,7 +60,7 @@ function createUploadPayload(fixturePath) { async function gotoDashboard(page) { await page.goto(`${BASE_URL}/dashboard`, { waitUntil: 'networkidle' }); - await expect(page.getByRole('columnheader', { name: 'File Name' })).toBeVisible({ timeout: 30_000 }); + await expect(page.getByRole('columnheader', { name: 'File Name' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Upload & Validate' })).toBeVisible(); await expect(page.locator('input[type="file"]').first()).toBeAttached(); }