@lowdefy/server-dev 6.1.0 → 7.1.0
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.
- package/client/App.jsx +52 -24
- package/client/Inspector.jsx +73 -30
- package/client/JourneyObserver.jsx +47 -0
- package/client/Page.jsx +54 -22
- package/client/Recorder.jsx +73 -0
- package/client/Routing.jsx +37 -13
- package/client/clientBuildImports.test.mjs +2 -0
- package/client/devStreamConnections.test.mjs +40 -0
- package/client/feedback/FeedbackMount.jsx +2 -2
- package/client/feedback/FeedbackOverlay.jsx +7 -4
- package/client/feedback/feedbackStyles.js +3 -0
- package/client/journeyObserver/startJourneyObserver.js +101 -0
- package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
- package/client/openInEditor/OpenInEditorListener.jsx +2 -0
- package/client/recorder/buildEngineRecord.js +37 -0
- package/client/recorder/buildInteractionRecord.js +64 -0
- package/client/recorder/buildTraceEvent.js +57 -0
- package/client/recorder/buildTraceEvent.test.mjs +81 -0
- package/client/recorder/chunkRecords.js +69 -0
- package/client/recorder/chunkRecords.test.mjs +59 -0
- package/client/recorder/collectEventActions.js +30 -0
- package/client/recorder/collectEventEndpoints.js +40 -0
- package/client/recorder/collectEventRequests.js +39 -0
- package/client/recorder/createPairingBuffer.js +187 -0
- package/client/recorder/createPairingBuffer.test.mjs +405 -0
- package/client/recorder/createPasswordRedactor.js +84 -0
- package/client/recorder/createRecordBatcher.js +54 -0
- package/client/recorder/createRecordBatcher.test.mjs +76 -0
- package/client/recorder/createRecorder.js +216 -0
- package/client/recorder/createRecorder.test.mjs +318 -0
- package/client/recorder/createRecordingSession.js +61 -0
- package/client/recorder/createRecordingSession.test.mjs +72 -0
- package/client/recorder/diffStateWrites.js +77 -0
- package/client/recorder/diffStateWrites.test.mjs +66 -0
- package/client/recorder/isEventCall.js +30 -0
- package/client/recorder/jsonType.js +39 -0
- package/client/recorder/keyChord.js +47 -0
- package/client/recorder/keyChord.test.mjs +50 -0
- package/client/recorder/sendRecords.js +40 -0
- package/client/recorder/toJsonSafe.js +29 -0
- package/lib/client/BuildingPage.jsx +3 -1
- package/lib/client/ErrorBar.jsx +17 -13
- package/lib/client/RedirectingPage.jsx +56 -0
- package/lib/client/auth/Auth.jsx +11 -6
- package/lib/client/auth/AuthConfigured.jsx +222 -36
- package/lib/client/auth/AuthNotConfigured.js +45 -4
- package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
- package/lib/client/auth/authTemplateSync.test.mjs +40 -0
- package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
- package/lib/client/auth/createFetchCheckingBuild.js +45 -0
- package/lib/client/bundleBuildId.js +25 -0
- package/lib/client/collectVisibleBlockIds.js +51 -0
- package/lib/client/getCspNonce.js +25 -0
- package/lib/client/getPagePath.js +36 -0
- package/lib/client/getPagePath.test.mjs +51 -0
- package/lib/client/utils/formatErrorsForCopy.js +30 -0
- package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
- package/lib/client/utils/getErrorBarColor.js +25 -0
- package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
- package/lib/client/utils/request.js +8 -1
- package/lib/client/utils/usePageConfig.js +89 -25
- package/lib/client/utils/usePageConfig.test.mjs +198 -13
- package/lib/configTree/buildConfigTree.mjs +58 -0
- package/lib/configTree/buildConfigTree.test.mjs +77 -0
- package/lib/configTree/collectConfigText.mjs +101 -0
- package/lib/configTree/collectConfigText.test.mjs +116 -0
- package/lib/configTree/prepareScratchServer.mjs +46 -0
- package/lib/configTree/runConfigTreeBuild.mjs +88 -0
- package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
- package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
- package/lib/docs/JourneyStepError.js +30 -0
- package/lib/docs/acquireBrowserSlot.js +30 -0
- package/lib/docs/addNestedEndpoints.js +54 -0
- package/lib/docs/addNestedEndpoints.test.mjs +185 -0
- package/lib/docs/addWriteFlags.js +77 -0
- package/lib/docs/buildEditedPages.js +50 -0
- package/lib/docs/buildWaitHeader.js +21 -0
- package/lib/docs/captureAnnotatedScreenshot.js +23 -12
- package/lib/docs/checkAgentFiles.js +148 -0
- package/lib/docs/checkAgentFiles.test.mjs +225 -0
- package/lib/docs/checkStepReferences.js +239 -0
- package/lib/docs/checkStepReferences.test.mjs +173 -0
- package/lib/docs/checkpointStore.js +3 -0
- package/lib/docs/clientErrorStore.js +18 -1
- package/lib/docs/clientErrorStore.test.mjs +46 -0
- package/lib/docs/collectExercised.js +52 -0
- package/lib/docs/createBrowserLifecycle.js +102 -0
- package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
- package/lib/docs/createDocsMcpServer.js +345 -388
- package/lib/docs/createDocsMcpServer.test.mjs +644 -4
- package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
- package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
- package/lib/docs/createJourneyActors.js +213 -0
- package/lib/docs/createJourneyActors.test.mjs +79 -0
- package/lib/docs/createJourneyObservations.js +80 -0
- package/lib/docs/createLeftOriginError.js +26 -0
- package/lib/docs/createNetworkCounter.js +101 -0
- package/lib/docs/dataSets/checkSharedCollectionIds.js +78 -0
- package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +727 -0
- package/lib/docs/dataSets/describeDataSetResult.js +25 -0
- package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
- package/lib/docs/dataSets/describeTargetKey.js +29 -0
- package/lib/docs/dataSets/deserializeFixture.js +34 -0
- package/lib/docs/dataSets/drainSessionWork.js +37 -0
- package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
- package/lib/docs/dataSets/duplicateIndexName.js +24 -0
- package/lib/docs/dataSets/getDataStore.js +106 -0
- package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
- package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +52 -0
- package/lib/docs/dataSets/insertFixture.js +57 -0
- package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
- package/lib/docs/dataSets/isDataStoreGone.js +29 -0
- package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
- package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
- package/lib/docs/dataSets/openDataSession.js +133 -0
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +493 -0
- package/lib/docs/dataSets/pickDataStorePort.js +58 -0
- package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
- package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
- package/lib/docs/dataSets/readDataSession.js +41 -0
- package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
- package/lib/docs/dataSets/readDataSet.js +83 -0
- package/lib/docs/dataSets/readDataSet.test.mjs +251 -0
- package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
- package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
- package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
- package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
- package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +36 -0
- package/lib/docs/dataSets/test/app/lowdefy.yaml +225 -0
- package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +12 -0
- package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
- package/lib/docs/devEventBus.js +136 -0
- package/lib/docs/devEventBus.test.mjs +269 -0
- package/lib/docs/devToolDefinitions.js +529 -0
- package/lib/docs/docSources.js +21 -0
- package/lib/docs/docs.test.mjs +171 -15
- package/lib/docs/docsIndex.test.mjs +377 -0
- package/lib/docs/evalOperator.js +5 -3
- package/lib/docs/evalOperatorHeadless.js +84 -12
- package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
- package/lib/docs/evalOperatorInTab.js +2 -1
- package/lib/docs/findConfig.js +95 -49
- package/lib/docs/fitResponse.js +81 -0
- package/lib/docs/fitResponse.test.mjs +128 -0
- package/lib/docs/formatFeedback.js +26 -5
- package/lib/docs/formatFeedback.test.mjs +48 -0
- package/lib/docs/getAppMap.js +7 -1
- package/lib/docs/getBrowser.js +150 -80
- package/lib/docs/getBrowser.test.mjs +407 -16
- package/lib/docs/getBuildId.js +61 -0
- package/lib/docs/getBuildId.test.mjs +84 -0
- package/lib/docs/getBuildStatus.js +81 -7
- package/lib/docs/getBuildStatus.test.mjs +229 -0
- package/lib/docs/getBuildStatusAfterEdits.js +35 -0
- package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
- package/lib/docs/getCoreDoc.js +9 -1
- package/lib/docs/getDoc.js +100 -0
- package/lib/docs/getDocsIndex.js +152 -0
- package/lib/docs/getHazards.js +115 -0
- package/lib/docs/getHazards.test.mjs +175 -0
- package/lib/docs/getMailOutboxDirectory.js +26 -0
- package/lib/docs/getOverview.js +79 -13
- package/lib/docs/getOverviewBasePath.test.mjs +37 -0
- package/lib/docs/getPageBuildStatus.js +46 -0
- package/lib/docs/getPluginDoc.js +21 -23
- package/lib/docs/getSchema.js +33 -1
- package/lib/docs/getStaleStatus.js +36 -0
- package/lib/docs/getStaleStatus.test.mjs +74 -0
- package/lib/docs/guardJourneyOrigin.js +39 -0
- package/lib/docs/inspectState.js +5 -3
- package/lib/docs/inspectStateFromTab.js +2 -2
- package/lib/docs/inspectStateHeadless.js +89 -27
- package/lib/docs/inspectStateHeadless.test.mjs +20 -1
- package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
- package/lib/docs/installHeadlessShell.js +123 -0
- package/lib/docs/installHeadlessShell.test.mjs +70 -0
- package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
- package/lib/docs/isHeadlessShellIncomplete.js +34 -0
- package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
- package/lib/docs/isPageReady.js +77 -0
- package/lib/docs/isPageReady.test.mjs +235 -0
- package/lib/docs/isRunErrorEntry.js +25 -0
- package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
- package/lib/docs/journeyRunRecording.js +35 -0
- package/lib/docs/journeyRunRecording.test.mjs +39 -0
- package/lib/docs/keepEventStreamAlive.js +68 -0
- package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
- package/lib/docs/launchBrowser.js +83 -0
- package/lib/docs/launchBrowser.test.mjs +175 -0
- package/lib/docs/listModuleDocEntries.js +69 -0
- package/lib/docs/listPackageDocFiles.js +67 -0
- package/lib/docs/listPluginDocEntries.js +111 -0
- package/lib/docs/listPlugins.js +9 -58
- package/lib/docs/listTypes.js +14 -6
- package/lib/docs/loadFrameworkHazards.js +40 -0
- package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
- package/lib/docs/loadState.js +103 -35
- package/lib/docs/makeModuleManifestDoc.js +174 -0
- package/lib/docs/matchExercisedPages.js +35 -0
- package/lib/docs/matchJourneyRoute.js +69 -0
- package/lib/docs/matchJourneyRoute.test.mjs +178 -0
- package/lib/docs/mergeNetworkSnapshots.js +49 -0
- package/lib/docs/nextJourneySequence.js +30 -0
- package/lib/docs/noBrowserError.js +22 -0
- package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
- package/lib/docs/observe/claimExpectedErrors.js +67 -0
- package/lib/docs/observe/createJourneyAppErrors.js +236 -0
- package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
- package/lib/docs/observe/evaluateInvariants.js +183 -0
- package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
- package/lib/docs/observe/findingKey.js +29 -0
- package/lib/docs/observe/hasNoEffect.js +31 -0
- package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
- package/lib/docs/observe/installStepObserver.js +72 -0
- package/lib/docs/observe/isAppApiUrl.js +33 -0
- package/lib/docs/observe/parseAppApiUrl.js +37 -0
- package/lib/docs/observe/readStepObserver.js +42 -0
- package/lib/docs/observe/relativeSource.js +29 -0
- package/lib/docs/observe/resolveConfigKeySource.js +33 -0
- package/lib/docs/observe/takeJourneyEvents.js +36 -0
- package/lib/docs/observe/toAppErrorFailure.js +61 -0
- package/lib/docs/observe/usesSearchStage.js +45 -0
- package/lib/docs/observe/waitForClientErrorReports.js +31 -0
- package/lib/docs/observe/watchJourneyContext.js +75 -0
- package/lib/docs/observe/withAppErrors.js +31 -0
- package/lib/docs/observe/withAppErrors.test.mjs +101 -0
- package/lib/docs/openJourney.js +94 -0
- package/lib/docs/openJourneyEmail.js +41 -0
- package/lib/docs/openJourneyEmail.test.mjs +187 -0
- package/lib/docs/pageBlocks.js +47 -0
- package/lib/docs/pageBuildStatus.test.mjs +103 -0
- package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
- package/lib/docs/rails/authEngineActions.js +53 -0
- package/lib/docs/rails/authEngineActions.test.mjs +69 -0
- package/lib/docs/rails/authEngineMethods.js +52 -0
- package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
- package/lib/docs/rails/checkAuthActionsRan.js +66 -0
- package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
- package/lib/docs/rails/checkClickRails.js +56 -0
- package/lib/docs/rails/createDataSetRails.js +82 -0
- package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
- package/lib/docs/rails/findExternalBlocks.js +147 -0
- package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
- package/lib/docs/rails/installAuthActionRecorder.js +56 -0
- package/lib/docs/rails/readAuthActionRecorder.js +32 -0
- package/lib/docs/rails/readBuiltArtifact.js +30 -0
- package/lib/docs/readConnectionPlugins.js +26 -0
- package/lib/docs/readDevAuthMode.js +32 -0
- package/lib/docs/readDocEntry.js +40 -0
- package/lib/docs/readDocTitle.js +26 -0
- package/lib/docs/readJourneyEmailMatch.js +57 -0
- package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
- package/lib/docs/readJourneySession.js +56 -0
- package/lib/docs/readMailOutbox.js +37 -0
- package/lib/docs/readPagePath.js +27 -0
- package/lib/docs/readPluginPackages.js +99 -0
- package/lib/docs/readProxyBuildWait.js +38 -0
- package/lib/docs/readRoutineSteps.js +45 -0
- package/lib/docs/registerShownTextEngine.js +118 -0
- package/lib/docs/requestRestart.js +30 -0
- package/lib/docs/requestRestart.test.mjs +49 -0
- package/lib/docs/resolvePageInstance.js +46 -0
- package/lib/docs/resolvePageInstance.test.mjs +81 -0
- package/lib/docs/resolveSource.js +11 -7
- package/lib/docs/resolveSource.test.mjs +1 -1
- package/lib/docs/resolveToolCaller.js +62 -0
- package/lib/docs/reviewPage.js +51 -0
- package/lib/docs/reviewPageBuilds.js +56 -0
- package/lib/docs/reviewPageBuilds.test.mjs +207 -0
- package/lib/docs/runCheck.js +75 -0
- package/lib/docs/runCheck.test.mjs +91 -0
- package/lib/docs/runCheckChild.js +49 -0
- package/lib/docs/runEndpoint.js +185 -0
- package/lib/docs/runEndpoint.test.mjs +509 -0
- package/lib/docs/runErrorBuffers.js +99 -0
- package/lib/docs/runErrorBuffers.test.mjs +95 -0
- package/lib/docs/runJourney.js +347 -0
- package/lib/docs/runJourney.test.mjs +2383 -0
- package/lib/docs/runJourneyAsUsers.js +44 -0
- package/lib/docs/runJourneyDataSet.test.mjs +274 -0
- package/lib/docs/runJourneySteps.js +1320 -0
- package/lib/docs/runRequest.js +100 -59
- package/lib/docs/runRequest.test.mjs +190 -0
- package/lib/docs/saveAnnotatedScreenshot.js +1 -1
- package/lib/docs/scaffoldPage.js +3 -1
- package/lib/docs/screenshotPage.js +163 -11
- package/lib/docs/screenshotPage.test.mjs +263 -1
- package/lib/docs/screenshotPageClip.test.mjs +4 -0
- package/lib/docs/searchDocs.js +82 -30
- package/lib/docs/searchDocs.test.mjs +100 -0
- package/lib/docs/searchIcons.js +90 -0
- package/lib/docs/searchIcons.test.mjs +127 -0
- package/lib/docs/selectExercised.js +39 -0
- package/lib/docs/selectFinalState.js +66 -0
- package/lib/docs/selectFinalState.test.mjs +51 -0
- package/lib/docs/serverErrorStore.js +54 -0
- package/lib/docs/serverErrorStore.test.mjs +50 -0
- package/lib/docs/setupTestFixtures.mjs +133 -39
- package/lib/docs/shownTextSelector.js +28 -0
- package/lib/docs/snapshotState.js +13 -5
- package/lib/docs/splitSearchTerms.js +62 -0
- package/lib/docs/splitSearchTerms.test.mjs +50 -0
- package/lib/docs/stateCheckpoints.test.mjs +77 -0
- package/lib/docs/tabAvailable.js +6 -5
- package/lib/docs/tabChannel.js +127 -21
- package/lib/docs/tabChannel.test.mjs +187 -29
- package/lib/docs/unsettledPageNote.js +24 -0
- package/lib/docs/validateJourneyMail.js +36 -0
- package/lib/docs/validateJourneyTimeout.js +38 -0
- package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
- package/lib/docs/validateRecordingParam.js +42 -0
- package/lib/docs/validateStateSelection.js +33 -0
- package/lib/docs/validateStateSelection.test.mjs +33 -0
- package/lib/docs/validateViewport.js +51 -0
- package/lib/docs/validateViewport.test.mjs +54 -0
- package/lib/docs/waitForBuild.js +50 -0
- package/lib/docs/waitForBuild.test.mjs +75 -0
- package/lib/docs/waitForJourneyEmail.js +77 -0
- package/lib/docs/withBrowserSlot.js +45 -0
- package/lib/docs/withBrowserSlot.test.mjs +75 -0
- package/lib/docs/withDataSession.js +53 -0
- package/lib/server/applyDataSetRedirect.js +92 -0
- package/lib/server/applyDataSetRedirect.test.mjs +446 -0
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +207 -0
- package/lib/server/applyDataSetRedirectWalled.test.mjs +134 -0
- package/lib/server/auth/createSystemContext.js +73 -0
- package/lib/server/auth/{getAuthConfig.js → getAuth.js} +15 -9
- package/lib/server/auth/getHeadlessUser.js +9 -8
- package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
- package/lib/server/auth/getMockUser.js +18 -14
- package/lib/server/auth/{session.js → getStrategies.js} +17 -16
- package/lib/server/auth/headlessUser.js +3 -2
- package/lib/server/auth/journeyActor.js +35 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
- package/lib/server/checkPageRecord.js +44 -0
- package/lib/server/contextMapBudget.js +24 -0
- package/lib/server/contextMapBudget.test.mjs +98 -0
- package/lib/server/continueJitKeys.js +28 -0
- package/lib/server/continueJitKeys.test.mjs +43 -0
- package/lib/server/createDataSessionEndedError.js +33 -0
- package/lib/server/createDataSetRefusal.js +26 -0
- package/lib/server/createLowdefyContext.js +168 -41
- package/lib/server/createLowdefyContext.test.mjs +331 -8
- package/lib/server/getClientAddress.js +51 -0
- package/lib/server/getClientAddress.test.mjs +56 -0
- package/lib/server/getPageJitEnrichment.test.mjs +29 -11
- package/lib/server/hashConfigContent.js +26 -0
- package/lib/server/importAppCode.test.mjs +57 -0
- package/lib/server/isRoutineStep.js +27 -0
- package/lib/server/jitPageBuilder.js +309 -126
- package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
- package/lib/server/jitPageBuilder.test.mjs +218 -0
- package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
- package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
- package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
- package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
- package/lib/server/journeyCookies.js +22 -0
- package/lib/server/journeyCookies.test.mjs +145 -0
- package/lib/server/loadDynamicJsMap.js +47 -0
- package/lib/server/log/createHandleError.js +38 -2
- package/lib/server/log/createHandleError.test.mjs +125 -2
- package/lib/server/log/createLogger.js +3 -0
- package/lib/server/log/createLogger.test.mjs +15 -0
- package/lib/server/log/logRequest.js +2 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +115 -0
- package/lib/server/mutants/applyMutant.js +39 -0
- package/lib/server/mutants/artifactPatterns.js +25 -0
- package/lib/server/mutants/buildAnchor.js +45 -0
- package/lib/server/mutants/collectSharedIds.js +41 -0
- package/lib/server/mutants/configPathSegments.js +38 -0
- package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
- package/lib/server/mutants/describeEvent.js +22 -0
- package/lib/server/mutants/enumerateArtifact.js +56 -0
- package/lib/server/mutants/findNodeByKey.js +45 -0
- package/lib/server/mutants/findNodeByPointer.js +49 -0
- package/lib/server/mutants/getArtifactKind.js +32 -0
- package/lib/server/mutants/groupMutantCopies.js +56 -0
- package/lib/server/mutants/hashArtifact.js +25 -0
- package/lib/server/mutants/hashMutantTarget.js +34 -0
- package/lib/server/mutants/listMutants.js +122 -0
- package/lib/server/mutants/listMutants.test.mjs +502 -0
- package/lib/server/mutants/mutantId.js +30 -0
- package/lib/server/mutants/mutantRuns.js +57 -0
- package/lib/server/mutants/mutantRuns.test.mjs +136 -0
- package/lib/server/mutants/operators/dropAction.js +47 -0
- package/lib/server/mutants/operators/dropBlock.js +44 -0
- package/lib/server/mutants/operators/dropPayload.js +52 -0
- package/lib/server/mutants/operators/dropStep.js +44 -0
- package/lib/server/mutants/operators/flipVisible.js +53 -0
- package/lib/server/mutants/operators/index.js +37 -0
- package/lib/server/mutants/operators/retargetLink.js +72 -0
- package/lib/server/mutants/operators/skipValidate.js +77 -0
- package/lib/server/mutants/operators/swapIf.js +112 -0
- package/lib/server/mutants/operators.test.mjs +396 -0
- package/lib/server/mutants/positionFreePath.js +38 -0
- package/lib/server/mutants/removeFromParent.js +28 -0
- package/lib/server/mutants/test/fixtures.mjs +173 -0
- package/lib/server/mutants/test/keyArtifact.mjs +63 -0
- package/lib/server/mutants/validateMutantParam.js +69 -0
- package/lib/server/mutants/validateMutantsBody.js +88 -0
- package/lib/server/mutants/walkArtifact.js +179 -0
- package/lib/server/pageBuildRecords.js +131 -0
- package/lib/server/pageBuildRecords.test.mjs +258 -0
- package/lib/server/pageCache.mjs +22 -7
- package/lib/server/pageCache.test.mjs +22 -7
- package/lib/server/pageRebuilds.test.mjs +543 -0
- package/lib/server/pruneJitMaps.js +36 -0
- package/lib/server/pruneJitMaps.test.mjs +55 -0
- package/lib/server/readInstanceRecord.js +33 -0
- package/lib/server/readInstanceRecord.test.mjs +99 -0
- package/lib/server/readMergedMaps.js +86 -0
- package/lib/server/readMergedMaps.test.mjs +79 -0
- package/lib/server/recording/appendRecords.js +33 -0
- package/lib/server/recording/errorRecordingStamp.js +34 -0
- package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
- package/lib/server/recording/isRecordingEnabled.js +31 -0
- package/lib/server/recording/readRecordingCookie.js +71 -0
- package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
- package/lib/server/recording/recordingCookiePayload.js +33 -0
- package/lib/server/recording/servedBuilds.js +46 -0
- package/lib/server/recording/servedBuilds.test.mjs +42 -0
- package/lib/server/sinkExternalConnections.js +95 -0
- package/lib/server/sinkExternalConnections.test.mjs +135 -0
- package/lib/server/skipStaleMapWrites.js +62 -0
- package/lib/server/skipStaleMapWrites.test.mjs +113 -0
- package/lib/server/startIdleGc.js +67 -0
- package/lib/server/startIdleGc.test.mjs +138 -0
- package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
- package/lib/server/trackSessionWork.js +30 -0
- package/lib/vite/createDevServerExclude.js +40 -0
- package/lib/vite/createDevServerExclude.test.mjs +79 -0
- package/manager/getContext.mjs +25 -3
- package/manager/processes/initialBuild.mjs +17 -1
- package/manager/processes/initialBuild.test.mjs +104 -0
- package/manager/processes/lowdefyBuild.mjs +31 -7
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +72 -0
- package/manager/processes/lowdefyBuild.test.mjs +136 -1
- package/manager/processes/optimizeDependencies.mjs +59 -0
- package/manager/processes/restartServer.mjs +34 -6
- package/manager/processes/restartServer.test.mjs +162 -0
- package/manager/processes/shutdownServer.mjs +11 -0
- package/manager/processes/shutdownServer.test.mjs +45 -0
- package/manager/processes/startFirstServer.mjs +46 -0
- package/manager/processes/startFirstServer.test.mjs +326 -0
- package/manager/processes/startMailSink.mjs +135 -0
- package/manager/processes/startMailSink.test.mjs +146 -0
- package/manager/processes/startProxy.mjs +129 -12
- package/manager/processes/startProxy.test.mjs +366 -0
- package/manager/processes/startServer.mjs +47 -6
- package/manager/processes/startServer.test.mjs +155 -0
- package/manager/processes/startWatchers.mjs +6 -4
- package/manager/processes/syncServer.mjs +69 -0
- package/manager/processes/syncServer.test.mjs +141 -0
- package/manager/processes/waitForDevServer.mjs +48 -0
- package/manager/processes/waitForDevServer.test.mjs +112 -0
- package/manager/run.mjs +128 -43
- package/manager/utils/BatchChanges.mjs +21 -5
- package/manager/utils/BatchChanges.test.mjs +77 -0
- package/manager/utils/acquireDevInstance.mjs +99 -0
- package/manager/utils/acquireDevInstance.test.mjs +160 -0
- package/manager/utils/createBuildActivity.mjs +67 -0
- package/manager/utils/createBuildActivity.test.mjs +58 -0
- package/manager/utils/createChangeSignal.mjs +39 -0
- package/manager/utils/createChangeSignal.test.mjs +62 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
- package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
- package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
- package/manager/utils/createDotPathIgnore.mjs +44 -0
- package/manager/utils/createDotPathIgnore.test.mjs +65 -0
- package/manager/utils/createIdleGc.mjs +51 -0
- package/manager/utils/createIdleGc.test.mjs +77 -0
- package/manager/utils/createRequestActivity.mjs +100 -0
- package/manager/utils/createRequestActivity.test.mjs +121 -0
- package/manager/utils/createServerArtifactTracker.mjs +83 -0
- package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
- package/manager/utils/createServerEnv.mjs +39 -0
- package/manager/utils/filesHaveSameBytes.mjs +41 -0
- package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
- package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
- package/manager/utils/importFresh.mjs +46 -0
- package/manager/utils/importFresh.test.mjs +72 -0
- package/manager/utils/importFreshWorker.mjs +20 -0
- package/manager/utils/killTaggedBrowser.mjs +35 -0
- package/manager/utils/pagePathChanged.mjs +56 -0
- package/manager/utils/pruneRecordings.mjs +85 -0
- package/manager/utils/pruneRecordings.test.mjs +117 -0
- package/manager/utils/publishBuildDirectory.mjs +121 -0
- package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
- package/manager/utils/publishBuildDirectory.test.mjs +262 -0
- package/manager/utils/readBasePath.mjs +27 -0
- package/manager/utils/readBasePath.test.mjs +43 -0
- package/manager/utils/readBuildStatusWait.mjs +67 -0
- package/manager/utils/readManagerVersion.mjs +30 -0
- package/manager/utils/readManagerVersion.test.mjs +35 -0
- package/manager/utils/readPluginDefinitions.mjs +36 -0
- package/manager/utils/resolveDevAuthUrl.mjs +58 -0
- package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
- package/manager/utils/resolvePorts.mjs +49 -0
- package/manager/utils/setupWatcher.mjs +19 -11
- package/manager/utils/startRecordingPruner.mjs +45 -0
- package/manager/utils/startRecordingPruner.test.mjs +66 -0
- package/manager/utils/waitForChildExit.mjs +53 -0
- package/manager/utils/waitForChildExit.test.mjs +70 -0
- package/manager/utils/waitForServer.mjs +44 -0
- package/manager/utils/writeBuildStatus.mjs +1 -1
- package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
- package/manager/watchers/envWatcher.mjs +9 -2
- package/manager/watchers/envWatcher.test.mjs +87 -0
- package/manager/watchers/listServerSidePackages.mjs +47 -0
- package/manager/watchers/listServerSidePackages.test.mjs +38 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
- package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
- package/manager/watchers/pluginSourceWatcher.mjs +79 -0
- package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
- package/manager/watchers/restartRequestWatcher.mjs +71 -0
- package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
- package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
- package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
- package/manager/watchers/serverPackageWatcher.mjs +38 -0
- package/package.json +60 -47
- package/package.original.json +60 -47
- package/src/app.js +131 -20
- package/src/html/renderDevPage.js +63 -11
- package/src/html/renderDevPage.test.mjs +138 -0
- package/src/lib/getRequestPath.js +29 -0
- package/src/middleware/createSameOriginGuard.js +37 -0
- package/src/middleware/createSameOriginGuard.test.mjs +102 -0
- package/src/middleware/errorHandler.js +51 -1
- package/src/middleware/errorHandler.test.mjs +173 -3
- package/src/middleware/isRebindingSafeHost.js +44 -0
- package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
- package/src/middleware/localDevToolsOnly.js +38 -0
- package/src/middleware/localDevToolsOnly.test.mjs +61 -0
- package/src/middleware/noStore.js +27 -0
- package/src/middleware/noStore.test.mjs +48 -0
- package/src/middleware/requestTimeout.js +38 -0
- package/src/middleware/staleFlag.js +76 -0
- package/src/middleware/staleFlag.test.mjs +127 -0
- package/src/middleware/stampCaller.js +38 -0
- package/src/middleware/stampCaller.test.mjs +64 -0
- package/src/routes/agent.js +15 -3
- package/src/routes/auth.js +29 -20
- package/src/routes/auth.test.mjs +30 -56
- package/src/routes/clientError.js +13 -13
- package/src/routes/cron.js +3 -1
- package/src/routes/cronForward.js +3 -1
- package/src/routes/detached.js +22 -9
- package/src/routes/devInspect.js +17 -8
- package/src/routes/devInspect.test.mjs +44 -7
- package/src/routes/devRecording.js +113 -0
- package/src/routes/devRecording.test.mjs +253 -0
- package/src/routes/docs/buildStatus.js +10 -2
- package/src/routes/docs/buildStatus.test.mjs +46 -0
- package/src/routes/docs/content.js +5 -3
- package/src/routes/docs/evalOperator.js +15 -1
- package/src/routes/docs/events.js +50 -0
- package/src/routes/docs/events.test.mjs +117 -0
- package/src/routes/docs/find.js +9 -1
- package/src/routes/docs/icons.js +30 -0
- package/src/routes/docs/inspectState.js +16 -1
- package/src/routes/docs/journey.js +194 -0
- package/src/routes/docs/journey.test.mjs +401 -0
- package/src/routes/docs/journeyRefusals.test.mjs +71 -0
- package/src/routes/docs/loadState.js +4 -2
- package/src/routes/docs/mcp.js +34 -3
- package/src/routes/docs/mcp.test.mjs +130 -0
- package/src/routes/docs/mutants.js +78 -0
- package/src/routes/docs/mutants.test.mjs +180 -0
- package/src/routes/docs/parsePathParamsParam.js +41 -0
- package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
- package/src/routes/docs/parseUserParam.js +27 -21
- package/src/routes/docs/parseUserParam.test.mjs +17 -5
- package/src/routes/docs/restart.js +35 -0
- package/src/routes/docs/restart.test.mjs +64 -0
- package/src/routes/docs/runEndpoint.js +54 -0
- package/src/routes/docs/runEndpoint.test.mjs +165 -0
- package/src/routes/docs/runRequest.js +25 -8
- package/src/routes/docs/runRequest.test.mjs +92 -0
- package/src/routes/docs/screenshot.js +50 -2
- package/src/routes/docs/screenshot.test.mjs +117 -0
- package/src/routes/docs/search.js +15 -2
- package/src/routes/docs/snapshotState.js +17 -3
- package/src/routes/endpoints.js +27 -26
- package/src/routes/endpointsTextAnswer.test.mjs +77 -0
- package/src/routes/feedback.js +10 -13
- package/src/routes/jitPage.js +99 -35
- package/src/routes/jitPage.test.mjs +248 -0
- package/src/routes/mcp.js +82 -5
- package/src/routes/mcp.test.mjs +215 -0
- package/src/routes/mcpMemberToken.test.mjs +125 -0
- package/src/routes/mcpProtectedResourceMetadata.js +34 -0
- package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
- package/src/routes/mountDevPageRoutes.js +25 -0
- package/src/routes/mountDevPageRoutes.test.mjs +30 -0
- package/src/routes/mountOauthDiscovery.js +44 -0
- package/src/routes/mountOauthDiscovery.test.mjs +79 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
- package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
- package/src/routes/reload.js +16 -0
- package/src/routes/request.js +6 -4
- package/src/routes/request.test.mjs +9 -5
- package/src/routes/requestBody.test.mjs +159 -0
- package/src/routes/usage.js +6 -2
- package/src/routes/user.js +27 -0
- package/src/routes/websocket.js +29 -8
- package/src/routes/websocket.test.mjs +65 -0
- package/src/routes/wellKnownFallback.js +30 -0
- package/src/routes/wellKnownFallback.test.mjs +32 -0
- package/src/websocket/devWebSocket.js +41 -26
- package/src/websocket/devWebSocket.test.mjs +157 -0
- package/vite.config.js +53 -23
- package/lib/server/auth/getDevSession.js +0 -70
- package/lib/server/auth/getDevSession.test.mjs +0 -138
- package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
- package/manager/watchers/moduleBuildWatcher.mjs +0 -79
- package/manager/watchers/serverArtifactWatcher.mjs +0 -120
- package/src/routes/apiPage.js +0 -37
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { spawn, spawnSync } from 'child_process';
|
|
18
|
+
import { createRequire } from 'module';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
import { type } from '@lowdefy/helpers';
|
|
22
|
+
|
|
23
|
+
// Read the way Playwright's own installer reads it, so a value that turns
|
|
24
|
+
// Playwright's download off turns this one off too.
|
|
25
|
+
function isDownloadSkipped() {
|
|
26
|
+
const value = process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD;
|
|
27
|
+
if (type.isNone(value) || value === '' || value === 'false' || value === '0') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The download is about 100 MB. Playwright 1.59's installer can stall for
|
|
34
|
+
// good while unzipping on Node 26, so an install that has not finished by
|
|
35
|
+
// then is treated as failed.
|
|
36
|
+
const INSTALL_TIMEOUT_MS = 3 * 60 * 1000;
|
|
37
|
+
|
|
38
|
+
// The installer forks a download process of its own, which holds the zip
|
|
39
|
+
// open while it hangs, so its children go too. Only processes this server
|
|
40
|
+
// started are signalled.
|
|
41
|
+
function killInstaller(installer) {
|
|
42
|
+
if (process.platform === 'win32') {
|
|
43
|
+
spawnSync('taskkill', ['/pid', String(installer.pid), '/T', '/F'], { stdio: 'ignore' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
spawnSync('pkill', ['-KILL', '-P', String(installer.pid)], { stdio: 'ignore' });
|
|
47
|
+
installer.kill('SIGKILL');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runInstall() {
|
|
51
|
+
// cli.js is not in playwright-core's exports, so it is found next to the
|
|
52
|
+
// package.json, which is. Running the installer of the playwright-core this
|
|
53
|
+
// server loads fetches exactly the shell revision it launches.
|
|
54
|
+
const require = createRequire(import.meta.url);
|
|
55
|
+
const cliPath = path.join(
|
|
56
|
+
path.dirname(require.resolve('playwright-core/package.json')),
|
|
57
|
+
'cli.js'
|
|
58
|
+
);
|
|
59
|
+
console.info('Installing chromium-headless-shell for the dev server browser tools.');
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
let settled = false;
|
|
62
|
+
let stderr = '';
|
|
63
|
+
let timer = null;
|
|
64
|
+
function settle({ installed, reason }) {
|
|
65
|
+
if (settled) return;
|
|
66
|
+
settled = true;
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
if (!installed) {
|
|
69
|
+
console.warn(
|
|
70
|
+
`Could not install chromium-headless-shell (${reason}). The dev server browser tools use system Chrome when it is installed. Run: npx playwright install chromium-headless-shell`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
resolve({ installed, reason });
|
|
74
|
+
}
|
|
75
|
+
// A short-lived process: the download and unzip never touch this
|
|
76
|
+
// server's memory. Playwright's registry lock serialises installs from
|
|
77
|
+
// several dev servers on one machine.
|
|
78
|
+
const installer = spawn(process.execPath, [cliPath, 'install', 'chromium-headless-shell'], {
|
|
79
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
80
|
+
});
|
|
81
|
+
timer = setTimeout(() => {
|
|
82
|
+
killInstaller(installer);
|
|
83
|
+
settle({
|
|
84
|
+
installed: false,
|
|
85
|
+
reason: `the install did not finish within ${
|
|
86
|
+
INSTALL_TIMEOUT_MS / 60000
|
|
87
|
+
} minutes; Playwright's installer can stall while unzipping on Node 26, where installing with Node 22 works`,
|
|
88
|
+
});
|
|
89
|
+
}, INSTALL_TIMEOUT_MS);
|
|
90
|
+
// Never the reason the dev server stays up.
|
|
91
|
+
timer.unref();
|
|
92
|
+
installer.stderr.on('data', (data) => {
|
|
93
|
+
stderr = `${stderr}${data.toString('utf8')}`.slice(-1000);
|
|
94
|
+
});
|
|
95
|
+
installer.on('error', (error) => settle({ installed: false, reason: error.message }));
|
|
96
|
+
installer.on('exit', (code) => {
|
|
97
|
+
if (code === 0) {
|
|
98
|
+
settle({ installed: true });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
settle({ installed: false, reason: stderr.trim() || `exit code ${code}` });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// One install per dev server process, shared by every caller. A failed
|
|
107
|
+
// install is not retried in this process: system Chrome stays the fallback.
|
|
108
|
+
let installPromise = null;
|
|
109
|
+
|
|
110
|
+
// Resolves { installed: true } once the shell is installed, or
|
|
111
|
+
// { installed: false, reason } when the install failed or timed out. Returns
|
|
112
|
+
// null when downloads are turned off.
|
|
113
|
+
function installHeadlessShell() {
|
|
114
|
+
if (isDownloadSkipped()) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (type.isNone(installPromise)) {
|
|
118
|
+
installPromise = runInstall();
|
|
119
|
+
}
|
|
120
|
+
return installPromise;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default installHeadlessShell;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EventEmitter } from 'node:events';
|
|
18
|
+
|
|
19
|
+
import { jest } from '@jest/globals';
|
|
20
|
+
|
|
21
|
+
const mockSpawn = jest.fn();
|
|
22
|
+
jest.unstable_mockModule('child_process', () => ({ spawn: mockSpawn, spawnSync: jest.fn() }));
|
|
23
|
+
|
|
24
|
+
// installHeadlessShell keeps one install per process in module state, so the
|
|
25
|
+
// tests below run in order against one module: the skipped calls first, then
|
|
26
|
+
// the one install. The failure path is in installHeadlessShell.failure.test.mjs.
|
|
27
|
+
const { default: installHeadlessShell } = await import('./installHeadlessShell.js');
|
|
28
|
+
|
|
29
|
+
function createInstaller() {
|
|
30
|
+
return Object.assign(new EventEmitter(), { stderr: new EventEmitter() });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
jest.spyOn(console, 'info').mockImplementation(() => {});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
jest.restoreAllMocks();
|
|
39
|
+
delete process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test.each(['1', 'true'])(
|
|
43
|
+
'installHeadlessShell installs nothing when PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD is %s',
|
|
44
|
+
(value) => {
|
|
45
|
+
process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = value;
|
|
46
|
+
|
|
47
|
+
expect(installHeadlessShell()).toBeNull();
|
|
48
|
+
expect(mockSpawn).not.toHaveBeenCalled();
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
test("installHeadlessShell runs playwright-core's own installer once per process, however many calls arrive", async () => {
|
|
53
|
+
// Playwright reads "0" and "false" as unset.
|
|
54
|
+
process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = 'false';
|
|
55
|
+
const installer = createInstaller();
|
|
56
|
+
mockSpawn.mockReturnValue(installer);
|
|
57
|
+
|
|
58
|
+
const first = installHeadlessShell();
|
|
59
|
+
const second = installHeadlessShell();
|
|
60
|
+
installer.emit('exit', 0);
|
|
61
|
+
|
|
62
|
+
expect(await first).toEqual({ installed: true });
|
|
63
|
+
expect(second).toBe(first);
|
|
64
|
+
expect(installHeadlessShell()).toBe(first);
|
|
65
|
+
expect(mockSpawn).toHaveBeenCalledTimes(1);
|
|
66
|
+
const [command, args] = mockSpawn.mock.calls[0];
|
|
67
|
+
expect(command).toBe(process.execPath);
|
|
68
|
+
expect(args[0]).toMatch(/playwright-core[/\\]cli\.js$/);
|
|
69
|
+
expect(args.slice(1)).toEqual(['install', 'chromium-headless-shell']);
|
|
70
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { EventEmitter } from 'node:events';
|
|
18
|
+
|
|
19
|
+
import { jest } from '@jest/globals';
|
|
20
|
+
|
|
21
|
+
const mockSpawn = jest.fn();
|
|
22
|
+
const mockSpawnSync = jest.fn();
|
|
23
|
+
jest.unstable_mockModule('child_process', () => ({ spawn: mockSpawn, spawnSync: mockSpawnSync }));
|
|
24
|
+
|
|
25
|
+
// A file of its own: installHeadlessShell keeps its one install per process
|
|
26
|
+
// in module state.
|
|
27
|
+
const { default: installHeadlessShell } = await import('./installHeadlessShell.js');
|
|
28
|
+
|
|
29
|
+
test('installHeadlessShell kills an install that has not finished within 3 minutes and gives up', async () => {
|
|
30
|
+
// performance is not redefinable on this Node, and nothing here reads it.
|
|
31
|
+
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
32
|
+
const warn = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
|
33
|
+
jest.spyOn(console, 'info').mockImplementation(() => {});
|
|
34
|
+
try {
|
|
35
|
+
const installer = Object.assign(new EventEmitter(), {
|
|
36
|
+
pid: 4242,
|
|
37
|
+
stderr: new EventEmitter(),
|
|
38
|
+
kill: jest.fn(),
|
|
39
|
+
});
|
|
40
|
+
mockSpawn.mockReturnValue(installer);
|
|
41
|
+
|
|
42
|
+
const install = installHeadlessShell();
|
|
43
|
+
jest.advanceTimersByTime(3 * 60 * 1000 - 1);
|
|
44
|
+
expect(installer.kill).not.toHaveBeenCalled();
|
|
45
|
+
jest.advanceTimersByTime(1);
|
|
46
|
+
|
|
47
|
+
const result = await install;
|
|
48
|
+
expect(result.installed).toBe(false);
|
|
49
|
+
expect(result.reason).toContain('did not finish within 3 minutes');
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
// taskkill /T stops the installer and its download process together.
|
|
52
|
+
expect(mockSpawnSync).toHaveBeenCalledWith('taskkill', ['/pid', '4242', '/T', '/F'], {
|
|
53
|
+
stdio: 'ignore',
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
// The installer's own download process goes too.
|
|
57
|
+
expect(mockSpawnSync).toHaveBeenCalledWith('pkill', ['-KILL', '-P', '4242'], {
|
|
58
|
+
stdio: 'ignore',
|
|
59
|
+
});
|
|
60
|
+
expect(installer.kill).toHaveBeenCalledWith('SIGKILL');
|
|
61
|
+
}
|
|
62
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
63
|
+
// The exit that follows the kill does not log again, and nothing retries.
|
|
64
|
+
installer.emit('exit', null, 'SIGKILL');
|
|
65
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
66
|
+
expect(installHeadlessShell()).toBe(install);
|
|
67
|
+
expect(mockSpawn).toHaveBeenCalledTimes(1);
|
|
68
|
+
} finally {
|
|
69
|
+
jest.useRealTimers();
|
|
70
|
+
jest.restoreAllMocks();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
|
|
19
|
+
// Playwright's installer writes INSTALLATION_COMPLETE into the browser
|
|
20
|
+
// directory last, after extracting and chmod-ing the executable. An install
|
|
21
|
+
// that was killed or crashed after the executable landed leaves a directory
|
|
22
|
+
// without it: chromium.launch() checks only the executable and fails with a
|
|
23
|
+
// launch error, while a new `playwright install` sees the missing marker and
|
|
24
|
+
// installs again. The registry is Playwright's own (an exported subpath of the
|
|
25
|
+
// pinned playwright-core), so the directory honours PLAYWRIGHT_BROWSERS_PATH.
|
|
26
|
+
async function isHeadlessShellIncomplete() {
|
|
27
|
+
const { browserDirectoryToMarkerFilePath, registry } = await import(
|
|
28
|
+
'playwright-core/lib/server/registry/index'
|
|
29
|
+
);
|
|
30
|
+
const { directory } = registry.findExecutable('chromium-headless-shell');
|
|
31
|
+
return fs.existsSync(directory) && !fs.existsSync(browserDirectoryToMarkerFilePath(directory));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default isHeadlessShellIncomplete;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
// Playwright's registry reads PLAYWRIGHT_BROWSERS_PATH when it loads, so it
|
|
22
|
+
// is set before the first import.
|
|
23
|
+
const browsersPath = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-shell-'));
|
|
24
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH = browsersPath;
|
|
25
|
+
|
|
26
|
+
const { registry } = await import('playwright-core/lib/server/registry/index');
|
|
27
|
+
const { default: isHeadlessShellIncomplete } = await import('./isHeadlessShellIncomplete.js');
|
|
28
|
+
|
|
29
|
+
const { directory } = registry.findExecutable('chromium-headless-shell');
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterAll(() => {
|
|
36
|
+
fs.rmSync(browsersPath, { recursive: true, force: true });
|
|
37
|
+
delete process.env.PLAYWRIGHT_BROWSERS_PATH;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('the shell directory resolves under PLAYWRIGHT_BROWSERS_PATH', () => {
|
|
41
|
+
expect(path.dirname(directory)).toBe(browsersPath);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('isHeadlessShellIncomplete is false when the shell was never installed', async () => {
|
|
45
|
+
expect(await isHeadlessShellIncomplete()).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('isHeadlessShellIncomplete is true when the executable exists without the INSTALLATION_COMPLETE marker', async () => {
|
|
49
|
+
const executable = registry.findExecutable('chromium-headless-shell').executablePath();
|
|
50
|
+
fs.mkdirSync(path.dirname(executable), { recursive: true });
|
|
51
|
+
fs.writeFileSync(executable, '');
|
|
52
|
+
|
|
53
|
+
expect(await isHeadlessShellIncomplete()).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('isHeadlessShellIncomplete is false once the install wrote its marker', async () => {
|
|
57
|
+
const executable = registry.findExecutable('chromium-headless-shell').executablePath();
|
|
58
|
+
fs.mkdirSync(path.dirname(executable), { recursive: true });
|
|
59
|
+
fs.writeFileSync(executable, '');
|
|
60
|
+
fs.writeFileSync(path.join(directory, 'INSTALLATION_COMPLETE'), '');
|
|
61
|
+
|
|
62
|
+
expect(await isHeadlessShellIncomplete()).toBe(false);
|
|
63
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Serialized into the browser by page.waitForFunction, so this function must
|
|
18
|
+
// be pure: it may reference nothing but `window`. An import or a closed-over
|
|
19
|
+
// binding would be undefined once the source is evaluated inside the page.
|
|
20
|
+
//
|
|
21
|
+
// Checks are ordered cheapest-first and return false on the first failure.
|
|
22
|
+
// A missing key always means "not configured", never "not finished" —
|
|
23
|
+
// iterating only the entries that exist gives that for free.
|
|
24
|
+
//
|
|
25
|
+
// It reads the instance the app is showing, the most recently rendered
|
|
26
|
+
// instance of the shown page, so a caller waits out a redirect (a protected
|
|
27
|
+
// page sending a signed-out caller to sign in) instead of waiting for a page
|
|
28
|
+
// that will never mount.
|
|
29
|
+
//
|
|
30
|
+
// No page is ready while the app is between pages: a client navigation (a
|
|
31
|
+
// Link, even one in onInit) has changed the URL, but window.lowdefy.pageId
|
|
32
|
+
// still names the page being left until the next page renders (the dev
|
|
33
|
+
// client's Routing.jsx and Page.jsx mark that span).
|
|
34
|
+
function isPageReady() {
|
|
35
|
+
const lowdefy = window.lowdefy;
|
|
36
|
+
if (lowdefy?._devNavigating === true) return false;
|
|
37
|
+
const shownKey = lowdefy?.pageInstances?.[lowdefy?.pageId]?.at(-1);
|
|
38
|
+
const context = lowdefy?.contexts?.[shownKey];
|
|
39
|
+
if (!context) return false;
|
|
40
|
+
|
|
41
|
+
if (context._internal?.onInitDone !== true) return false;
|
|
42
|
+
// runOnInitAsync is always invoked once runOnInit resolves, so this settles
|
|
43
|
+
// even for a page that declares no onInitAsync event.
|
|
44
|
+
if (context._internal?.onInitAsyncDone !== true) return false;
|
|
45
|
+
// The app onInitAsync runs once per app load, alongside the first page's events.
|
|
46
|
+
const appContext = lowdefy?.appContext;
|
|
47
|
+
if (appContext && appContext._internal?.onInitAsyncDone !== true) return false;
|
|
48
|
+
|
|
49
|
+
// Covers onMountAsync (fired without await by Block.js) and any other event
|
|
50
|
+
// still running, such as an onClick from an earlier tool call.
|
|
51
|
+
const blocks = Object.values(context._internal?.RootSlots?.map ?? {});
|
|
52
|
+
for (const block of blocks) {
|
|
53
|
+
const events = Object.values(block?.Events?.events ?? {});
|
|
54
|
+
if (events.some((event) => event?.loading === true)) return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const requests = Object.values(context.requests ?? {});
|
|
58
|
+
if (requests.some((calls) => calls?.[0]?.loading)) return false;
|
|
59
|
+
|
|
60
|
+
// A subscription is settled once it has connected or failed; a page with no
|
|
61
|
+
// subscriptions has {} and passes.
|
|
62
|
+
const channels = Object.values(context.websockets ?? {});
|
|
63
|
+
if (channels.some((channel) => channel?.connected !== true && !channel?.error)) return false;
|
|
64
|
+
|
|
65
|
+
// Lazy blocks on screen still showing their fallback (createLazyBlock in
|
|
66
|
+
// @lowdefy/block-utils). The global is absent until a lazy block first
|
|
67
|
+
// mounts, and absent means none are loading.
|
|
68
|
+
if ((window.__lowdefyLazyLoads ?? 0) > 0) return false;
|
|
69
|
+
// Tables holding their loading skeleton for its minimum time after the
|
|
70
|
+
// load (useSkeletonTiming in @lowdefy/blocks-antd): their rows are about to
|
|
71
|
+
// replace it. Absent, like the lazy count, until a table first holds one.
|
|
72
|
+
if ((window.__lowdefySkeletonHolds ?? 0) > 0) return false;
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default isPageReady;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import isPageReady from './isPageReady.js';
|
|
18
|
+
|
|
19
|
+
// isPageReady runs against `window` inside the browser, so each case installs
|
|
20
|
+
// a stub context on global.window and asserts the boolean.
|
|
21
|
+
function setWindow(context) {
|
|
22
|
+
global.window = {
|
|
23
|
+
lowdefy: {
|
|
24
|
+
pageId: 'home',
|
|
25
|
+
pageInstances: { home: ['page:home'] },
|
|
26
|
+
contexts: context === undefined ? {} : { 'page:home': context },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function settledContext({ blocks = {}, requests = {}, websockets = {} } = {}) {
|
|
32
|
+
return {
|
|
33
|
+
_internal: { onInitDone: true, onInitAsyncDone: true, RootSlots: { map: blocks } },
|
|
34
|
+
requests,
|
|
35
|
+
websockets,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const originalWindow = global.window;
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
global.window = originalWindow;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('isPageReady returns false when the page context does not exist', () => {
|
|
46
|
+
setWindow(undefined);
|
|
47
|
+
|
|
48
|
+
expect(isPageReady()).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('isPageReady reads the page the app shows', () => {
|
|
52
|
+
global.window = {
|
|
53
|
+
lowdefy: { contexts: { 'page:login': settledContext() }, pageInstances: {} },
|
|
54
|
+
};
|
|
55
|
+
expect(isPageReady()).toBe(false);
|
|
56
|
+
|
|
57
|
+
// A protected page redirected to sign in: the login page is what mounted.
|
|
58
|
+
global.window.lowdefy.pageId = 'login';
|
|
59
|
+
global.window.lowdefy.pageInstances.login = ['page:login'];
|
|
60
|
+
expect(isPageReady()).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('isPageReady reads the most recently rendered instance of the shown page', () => {
|
|
64
|
+
const loading = settledContext();
|
|
65
|
+
loading._internal.onInitDone = false;
|
|
66
|
+
global.window = {
|
|
67
|
+
lowdefy: {
|
|
68
|
+
pageId: 'ticket',
|
|
69
|
+
contexts: {
|
|
70
|
+
'page:ticket#tickets/s/1': settledContext(),
|
|
71
|
+
'page:ticket#tickets/s/2': loading,
|
|
72
|
+
},
|
|
73
|
+
pageInstances: { ticket: ['page:ticket#tickets/s/1', 'page:ticket#tickets/s/2'] },
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
expect(isPageReady()).toBe(false);
|
|
77
|
+
|
|
78
|
+
global.window.lowdefy.pageInstances.ticket = [
|
|
79
|
+
'page:ticket#tickets/s/2',
|
|
80
|
+
'page:ticket#tickets/s/1',
|
|
81
|
+
];
|
|
82
|
+
expect(isPageReady()).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('isPageReady returns false while a client navigation has not rendered the next page', () => {
|
|
86
|
+
// A Link in onInit pushed the next URL; the page being left is still the
|
|
87
|
+
// one window.lowdefy names, and its context has settled.
|
|
88
|
+
global.window = {
|
|
89
|
+
lowdefy: {
|
|
90
|
+
pageId: 'guarded',
|
|
91
|
+
pageInstances: { guarded: ['page:guarded'] },
|
|
92
|
+
contexts: { 'page:guarded': settledContext() },
|
|
93
|
+
_devNavigating: true,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
expect(isPageReady()).toBe(false);
|
|
97
|
+
|
|
98
|
+
// The next page rendered: window.lowdefy names it and waits on its context.
|
|
99
|
+
global.window.lowdefy._devNavigating = false;
|
|
100
|
+
global.window.lowdefy.pageId = 'home';
|
|
101
|
+
global.window.lowdefy.pageInstances.home = ['page:home'];
|
|
102
|
+
expect(isPageReady()).toBe(false);
|
|
103
|
+
global.window.lowdefy.contexts['page:home'] = settledContext();
|
|
104
|
+
expect(isPageReady()).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('isPageReady returns false when window.lowdefy is not defined', () => {
|
|
108
|
+
global.window = {};
|
|
109
|
+
|
|
110
|
+
expect(isPageReady()).toBe(false);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('isPageReady returns false while onInit has not finished', () => {
|
|
114
|
+
setWindow({ _internal: { RootSlots: { map: {} } }, requests: {} });
|
|
115
|
+
|
|
116
|
+
expect(isPageReady()).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('isPageReady returns false while onInitAsync has not finished', () => {
|
|
120
|
+
setWindow({ _internal: { onInitDone: true, RootSlots: { map: {} } }, requests: {} });
|
|
121
|
+
|
|
122
|
+
expect(isPageReady()).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('isPageReady returns false while the app onInitAsync has not finished', () => {
|
|
126
|
+
setWindow(settledContext());
|
|
127
|
+
global.window.lowdefy.appContext = { _internal: { onInitDone: true } };
|
|
128
|
+
expect(isPageReady()).toBe(false);
|
|
129
|
+
|
|
130
|
+
global.window.lowdefy.appContext._internal.onInitAsyncDone = true;
|
|
131
|
+
expect(isPageReady()).toBe(true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('isPageReady returns false while a block onMountAsync event is loading', () => {
|
|
135
|
+
setWindow(
|
|
136
|
+
settledContext({
|
|
137
|
+
blocks: { list: { Events: { events: { onMountAsync: { loading: true } } } } },
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(isPageReady()).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('isPageReady returns true when a block event has finished loading', () => {
|
|
145
|
+
setWindow(
|
|
146
|
+
settledContext({
|
|
147
|
+
blocks: { list: { Events: { events: { onMountAsync: { loading: false } } } } },
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(isPageReady()).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('isPageReady returns false while a request is loading', () => {
|
|
155
|
+
setWindow(settledContext({ requests: { get_users: [{ loading: true }] } }));
|
|
156
|
+
|
|
157
|
+
expect(isPageReady()).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('isPageReady returns true when a request has resolved', () => {
|
|
161
|
+
setWindow(settledContext({ requests: { get_users: [{ loading: false, response: [] }] } }));
|
|
162
|
+
|
|
163
|
+
expect(isPageReady()).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('isPageReady returns false while a websocket channel has not connected', () => {
|
|
167
|
+
setWindow(settledContext({ websockets: { orders: { connected: false, error: null } } }));
|
|
168
|
+
|
|
169
|
+
expect(isPageReady()).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test('isPageReady returns true when a websocket channel failed with an error', () => {
|
|
173
|
+
setWindow(settledContext({ websockets: { orders: { connected: false, error: 'refused' } } }));
|
|
174
|
+
|
|
175
|
+
expect(isPageReady()).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('isPageReady returns true when every websocket channel is connected', () => {
|
|
179
|
+
setWindow(settledContext({ websockets: { orders: { connected: true, error: null } } }));
|
|
180
|
+
|
|
181
|
+
expect(isPageReady()).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('isPageReady returns true for a settled page with blocks, requests and websockets', () => {
|
|
185
|
+
setWindow(
|
|
186
|
+
settledContext({
|
|
187
|
+
blocks: { list: { Events: { events: { onMountAsync: { loading: false } } } } },
|
|
188
|
+
requests: { get_users: [{ loading: false }] },
|
|
189
|
+
websockets: { orders: { connected: true, error: null } },
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
expect(isPageReady()).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('isPageReady returns true for a page with no blocks, requests or websockets', () => {
|
|
197
|
+
setWindow({ _internal: { onInitDone: true, onInitAsyncDone: true } });
|
|
198
|
+
|
|
199
|
+
expect(isPageReady()).toBe(true);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test('isPageReady returns false while a lazy block on the page is still loading', () => {
|
|
203
|
+
setWindow(settledContext());
|
|
204
|
+
global.window.__lowdefyLazyLoads = 1;
|
|
205
|
+
|
|
206
|
+
expect(isPageReady()).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('isPageReady returns true once every lazy block has loaded', () => {
|
|
210
|
+
setWindow(settledContext());
|
|
211
|
+
global.window.__lowdefyLazyLoads = 0;
|
|
212
|
+
|
|
213
|
+
expect(isPageReady()).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('isPageReady returns true when no lazy block has mounted and the counter is absent', () => {
|
|
217
|
+
setWindow(settledContext());
|
|
218
|
+
|
|
219
|
+
expect(global.window.__lowdefyLazyLoads).toBeUndefined();
|
|
220
|
+
expect(isPageReady()).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test('isPageReady returns false while a table holds its loading skeleton', () => {
|
|
224
|
+
setWindow(settledContext());
|
|
225
|
+
global.window.__lowdefySkeletonHolds = 1;
|
|
226
|
+
|
|
227
|
+
expect(isPageReady()).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('isPageReady returns true once no table holds its loading skeleton', () => {
|
|
231
|
+
setWindow(settledContext());
|
|
232
|
+
global.window.__lowdefySkeletonHolds = 0;
|
|
233
|
+
|
|
234
|
+
expect(isPageReady()).toBe(true);
|
|
235
|
+
});
|