├── .dockerignore ├── .eslintrc.json ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.yml │ ├── FEATURE_IMPROVEMENT.yml │ └── QUESTION.yml └── workflows │ ├── browser-beta.yml │ ├── browser-dev.yml │ ├── building-docker.yml │ ├── docker.yml │ ├── lint.yml │ ├── linux-chrome.yml │ ├── linux-firefox.yml │ ├── mac-m1.yml │ ├── mac.yml │ ├── safari.yml │ ├── unittests.yml │ └── windows.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── ROADMAP.md ├── SUPPORT.md ├── bin ├── browsertime.js └── browsertimeWebPageReplay.js ├── browserscripts ├── browser │ ├── cpuBenchmark.js │ ├── userAgent.js │ └── windowSize.js ├── pageinfo │ ├── cumulativeLayoutShift.js │ ├── cumulativeLayoutShiftInfo.js │ ├── documentHeight.js │ ├── documentSize.js │ ├── documentTitle.js │ ├── documentWidth.js │ ├── domElements.js │ ├── generator.js │ ├── interactionToNextPaintInfo.js │ ├── largestContentfulPaintInfo.js │ ├── loaf.js │ ├── navigationStartTime.js │ ├── nextHopProtocol.js │ ├── resources.js │ ├── responsive.js │ ├── url.js │ └── visualElements.js └── timings │ ├── .eslintrc │ ├── elementTimings.js │ ├── firstInput.js │ ├── firstPaint.js │ ├── interactionToNextPaint.js │ ├── largestContentfulPaint.js │ ├── loadEventEnd.js │ ├── navigationTiming.js │ ├── pageTimings.js │ ├── paintTiming.js │ ├── serverTimings.js │ ├── timeToContentfulPaint.js │ ├── timeToDomContentFlushed.js │ ├── timeToFirstInteractive.js │ ├── ttfb.js │ └── userTimings.js ├── browsertime.png ├── docker ├── adb │ ├── insecure_shared_adbkey │ └── insecure_shared_adbkey.pub ├── scripts │ └── start.sh └── webpagereplay │ ├── LICENSE │ ├── deterministic.js │ ├── linux │ ├── amd64 │ │ └── wpr │ └── arm64 │ │ └── wpr │ ├── wpr_cert.pem │ └── wpr_key.pem ├── eslint.config.mjs ├── index.js ├── jsdoc ├── README.md ├── jsdoc.json └── tutorials │ ├── 01-Introduction.md │ ├── 02-Running-Scripts.md │ ├── 03-Measurement-Commands.md │ ├── 04-Interact-with-the-page.md │ ├── 05-Interact-Browser.md │ ├── 06-Error-handling.md │ ├── 07-Debugging-Scripts.md │ ├── 08-Setting-Up-IntelliSense.md │ ├── 09-Examples.md │ ├── 10-Selenium.md │ ├── 11-Chrome-Devtools-Protocol.md │ ├── 12-Bidi.md │ ├── 13-Android.md │ ├── 18-Tips-and-tricks.md │ └── tutorials.json ├── lib ├── android │ ├── gnirehtet.js │ ├── index.js │ └── root.js ├── chrome │ ├── chromeDevtoolsProtocol.js │ ├── har.js │ ├── longTaskMetrics.js │ ├── networkManager.js │ ├── parseCpuTrace.js │ ├── settings │ │ ├── chromeAndroidOptions.js │ │ ├── chromeDesktopOptions.js │ │ └── traceCategories.js │ ├── traceCategoriesParser.js │ └── webdriver │ │ ├── builder.js │ │ ├── chromium.js │ │ ├── devtools │ │ ├── domComplete.js │ │ ├── element.js │ │ ├── fcp.js │ │ ├── inp.js │ │ ├── lcp.js │ │ ├── loadEventEnd.js │ │ ├── loaf.js │ │ ├── longtask.js │ │ └── ttfb.js │ │ ├── setupChromiumOptions.js │ │ └── traceUtilities.js ├── connectivity │ ├── humble.js │ ├── index.js │ └── trafficShapeParser.js ├── core │ ├── .eslintrc.json │ ├── engine │ │ ├── collector.js │ │ ├── command │ │ │ ├── actions.js │ │ │ ├── addText.js │ │ │ ├── android.js │ │ │ ├── bidi.js │ │ │ ├── cache.js │ │ │ ├── chromeDevToolsProtocol.js │ │ │ ├── chromeTrace.js │ │ │ ├── click.js │ │ │ ├── debug.js │ │ │ ├── element.js │ │ │ ├── geckoProfiler.js │ │ │ ├── javaScript.js │ │ │ ├── measure.js │ │ │ ├── meta.js │ │ │ ├── mouse │ │ │ │ ├── clickAndHold.js │ │ │ │ ├── contextClick.js │ │ │ │ ├── doubleClick.js │ │ │ │ ├── index.js │ │ │ │ ├── mouseMove.js │ │ │ │ └── singleClick.js │ │ │ ├── navigation.js │ │ │ ├── perfStats.js │ │ │ ├── screenshot.js │ │ │ ├── scroll.js │ │ │ ├── select.js │ │ │ ├── set.js │ │ │ ├── stopWatch.js │ │ │ ├── switch.js │ │ │ ├── util │ │ │ │ ├── lcpHighlightScript.js │ │ │ │ └── lsHighlightScript.js │ │ │ └── wait.js │ │ ├── commands.js │ │ ├── context.js │ │ ├── index.js │ │ ├── iteration.js │ │ └── run.js │ ├── pageCompleteChecks │ │ ├── defaultPageCompleteCheck.js │ │ ├── pageCompleteCheckByInactivity.js │ │ └── spaInactivity.js │ ├── seleniumRunner.js │ └── webdriver │ │ └── index.js ├── edge │ └── webdriver │ │ └── builder.js ├── firefox │ ├── firefoxBidi.js │ ├── geckoProfiler.js │ ├── memoryReport.js │ ├── networkManager.js │ ├── perfStats.js │ ├── settings │ │ ├── disableSafeBrowsingPreferences.js │ │ ├── disableTrackingProtectionPreferences.js │ │ ├── firefoxPreferences.js │ │ └── geckoProfilerDefaults.js │ └── webdriver │ │ ├── builder.js │ │ └── firefox.js ├── safari │ └── webdriver │ │ ├── builder.js │ │ └── safari.js ├── screenshot │ ├── defaults.js │ ├── index.js │ └── loadCustomJimp.js ├── support │ ├── browserScript.js │ ├── cli.js │ ├── dns.js │ ├── engineUtils.js │ ├── errors.js │ ├── fileUtil.js │ ├── filters.js │ ├── getPort.js │ ├── getViewPort.js │ ├── har │ │ └── index.js │ ├── images │ │ └── index.js │ ├── logging.js │ ├── pathToFolder.js │ ├── preURL.js │ ├── processes.js │ ├── setResourceTimingBufferSize.js │ ├── statistics.js │ ├── stop.js │ ├── storageManager.js │ ├── tcpdump.js │ ├── usbPower.js │ ├── userTiming.js │ ├── util.js │ └── xvfb.js └── video │ ├── defaults.js │ ├── postprocessing │ ├── finetune │ │ ├── addTextToVideo.js │ │ ├── convertFps.js │ │ ├── getFont.js │ │ ├── getTimingMetrics.js │ │ ├── index.js │ │ └── removeOrange.js │ └── visualmetrics │ │ ├── extraMetrics.js │ │ ├── getVideoMetrics.js │ │ └── visualMetrics.js │ ├── screenRecording │ ├── android │ │ └── recorder.js │ ├── desktop │ │ ├── convert.js │ │ ├── desktopRecorder.js │ │ ├── ffmpegRecorder.js │ │ └── osx │ │ │ ├── getSPDisplaysDataType.js │ │ │ └── getScreen.js │ ├── firefox │ │ └── firefoxWindowRecorder.js │ ├── ios │ │ ├── convertToMp4.js │ │ └── iosRecorder.js │ ├── iosSimulator │ │ ├── convertToMp4.js │ │ └── recorder.js │ ├── recorder.js │ └── setOrangeBackground.js │ └── video.js ├── package-lock.json ├── package.json ├── release.sh ├── test ├── commandtests │ ├── actionTest.js │ ├── chromeTest.js │ ├── clickTest.js │ ├── firefoxTest.js │ ├── measureTest.js │ ├── miscTest.js │ ├── scrollTest.js │ ├── stopWatch.js │ └── unified.js ├── data │ ├── browserscripts │ │ └── testscripts │ │ │ ├── empty.txt │ │ │ └── scriptTags.js │ ├── commandscripts │ │ ├── actions.cjs │ │ ├── chrome.cjs │ │ ├── clickAndMeasure.cjs │ │ ├── clickBackAndForth.cjs │ │ ├── firefox.cjs │ │ ├── measure.cjs │ │ ├── measureAlias.cjs │ │ ├── misc.cjs │ │ ├── mouse.cjs │ │ ├── scrollByPixel.cjs │ │ ├── scrollToBottom.cjs │ │ ├── stopWatch.cjs │ │ └── unified.cjs │ ├── exampleConfig.json │ ├── html │ │ ├── css │ │ │ └── main.css │ │ ├── dimple │ │ │ └── index.html │ │ ├── img │ │ │ └── pirate.png │ │ ├── js │ │ │ └── jquery-2.1.4.min.js │ │ ├── search │ │ │ └── index.html │ │ └── simple │ │ │ └── index.html │ ├── navigationscript │ │ ├── measure.cjs │ │ ├── multi.cjs │ │ ├── multiMac.cjs │ │ ├── navigateAndStartInTwoSteps.cjs │ │ ├── sameURLTwice.cjs │ │ ├── sameURLTwiceWithClick.cjs │ │ └── simple.cjs │ ├── pagecompletescripts │ │ └── pageComplete10sec.js │ ├── prepostscripts │ │ ├── postSample.cjs │ │ └── preSample.cjs │ ├── scripting │ │ ├── common.cjs │ │ ├── common.js │ │ ├── module.js │ │ └── module.mjs │ └── timings.json ├── engineTest.js ├── unittests │ ├── browserScriptsTest.js │ ├── harBuilderTest.js │ ├── statisticsTest.js │ ├── trafficShapingTest.js │ └── userTimingTest.js └── util │ ├── engine.js │ ├── httpserver.js │ └── setup.js ├── tsconfig.json ├── types ├── android │ ├── index.d.ts │ └── index.d.ts.map ├── chrome │ ├── parseCpuTrace.d.ts │ ├── parseCpuTrace.d.ts.map │ ├── traceCategoriesParser.d.ts │ ├── traceCategoriesParser.d.ts.map │ └── webdriver │ │ ├── traceUtilities.d.ts │ │ └── traceUtilities.d.ts.map ├── core │ └── engine │ │ ├── command │ │ ├── action.d.ts │ │ ├── action.d.ts.map │ │ ├── actions.d.ts │ │ ├── actions.d.ts.map │ │ ├── addText.d.ts │ │ ├── addText.d.ts.map │ │ ├── android.d.ts │ │ ├── android.d.ts.map │ │ ├── bidi.d.ts │ │ ├── bidi.d.ts.map │ │ ├── cache.d.ts │ │ ├── cache.d.ts.map │ │ ├── chromeDevToolsProtocol.d.ts │ │ ├── chromeDevToolsProtocol.d.ts.map │ │ ├── chromeTrace.d.ts │ │ ├── chromeTrace.d.ts.map │ │ ├── click.d.ts │ │ ├── click.d.ts.map │ │ ├── debug.d.ts │ │ ├── debug.d.ts.map │ │ ├── element.d.ts │ │ ├── element.d.ts.map │ │ ├── geckoProfiler.d.ts │ │ ├── geckoProfiler.d.ts.map │ │ ├── javaScript.d.ts │ │ ├── javaScript.d.ts.map │ │ ├── measure.d.ts │ │ ├── measure.d.ts.map │ │ ├── meta.d.ts │ │ ├── meta.d.ts.map │ │ ├── mouse │ │ │ ├── clickAndHold.d.ts │ │ │ ├── clickAndHold.d.ts.map │ │ │ ├── contextClick.d.ts │ │ │ ├── contextClick.d.ts.map │ │ │ ├── doubleClick.d.ts │ │ │ ├── doubleClick.d.ts.map │ │ │ ├── index.d.ts │ │ │ ├── index.d.ts.map │ │ │ ├── mouseMove.d.ts │ │ │ ├── mouseMove.d.ts.map │ │ │ ├── singleClick.d.ts │ │ │ └── singleClick.d.ts.map │ │ ├── navigation.d.ts │ │ ├── navigation.d.ts.map │ │ ├── perfStats.d.ts │ │ ├── perfStats.d.ts.map │ │ ├── screenshot.d.ts │ │ ├── screenshot.d.ts.map │ │ ├── scroll.d.ts │ │ ├── scroll.d.ts.map │ │ ├── select.d.ts │ │ ├── select.d.ts.map │ │ ├── set.d.ts │ │ ├── set.d.ts.map │ │ ├── stopWatch.d.ts │ │ ├── stopWatch.d.ts.map │ │ ├── switch.d.ts │ │ ├── switch.d.ts.map │ │ ├── util │ │ │ ├── lcpHighlightScript.d.ts │ │ │ ├── lcpHighlightScript.d.ts.map │ │ │ ├── lsHighlightScript.d.ts │ │ │ └── lsHighlightScript.d.ts.map │ │ ├── wait.d.ts │ │ └── wait.d.ts.map │ │ ├── commands.d.ts │ │ ├── commands.d.ts.map │ │ ├── context.d.ts │ │ └── context.d.ts.map ├── firefox │ ├── geckoProfiler.d.ts │ ├── geckoProfiler.d.ts.map │ ├── perfStats.d.ts │ ├── perfStats.d.ts.map │ └── settings │ │ ├── geckoProfilerDefaults.d.ts │ │ └── geckoProfilerDefaults.d.ts.map ├── scripting.d.ts ├── support │ ├── engineUtils.d.ts │ ├── engineUtils.d.ts.map │ ├── errors.d.ts │ ├── errors.d.ts.map │ ├── fileUtil.d.ts │ ├── fileUtil.d.ts.map │ ├── filters.d.ts │ ├── filters.d.ts.map │ ├── getViewPort.d.ts │ ├── getViewPort.d.ts.map │ ├── pathToFolder.d.ts │ ├── pathToFolder.d.ts.map │ ├── storageManager.d.ts │ ├── storageManager.d.ts.map │ ├── tcpdump.d.ts │ ├── tcpdump.d.ts.map │ ├── usbPower.d.ts │ ├── usbPower.d.ts.map │ ├── userTiming.d.ts │ ├── userTiming.d.ts.map │ ├── util.d.ts │ └── util.d.ts.map └── video │ ├── defaults.d.ts │ ├── defaults.d.ts.map │ ├── postprocessing │ ├── finetune │ │ ├── addTextToVideo.d.ts │ │ ├── addTextToVideo.d.ts.map │ │ ├── convertFps.d.ts │ │ ├── convertFps.d.ts.map │ │ ├── getFont.d.ts │ │ ├── getFont.d.ts.map │ │ ├── getTimingMetrics.d.ts │ │ ├── getTimingMetrics.d.ts.map │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── removeOrange.d.ts │ │ └── removeOrange.d.ts.map │ └── visualmetrics │ │ ├── extraMetrics.d.ts │ │ ├── extraMetrics.d.ts.map │ │ ├── getVideoMetrics.d.ts │ │ ├── getVideoMetrics.d.ts.map │ │ ├── visualMetrics.d.ts │ │ └── visualMetrics.d.ts.map │ ├── screenRecording │ ├── android │ │ ├── recorder.d.ts │ │ └── recorder.d.ts.map │ ├── desktop │ │ ├── convert.d.ts │ │ ├── convert.d.ts.map │ │ ├── desktopRecorder.d.ts │ │ ├── desktopRecorder.d.ts.map │ │ ├── ffmpegRecorder.d.ts │ │ ├── ffmpegRecorder.d.ts.map │ │ └── osx │ │ │ ├── getSPDisplaysDataType.d.ts │ │ │ ├── getSPDisplaysDataType.d.ts.map │ │ │ ├── getScreen.d.ts │ │ │ └── getScreen.d.ts.map │ ├── firefox │ │ ├── firefoxWindowRecorder.d.ts │ │ └── firefoxWindowRecorder.d.ts.map │ ├── ios │ │ ├── convertToMp4.d.ts │ │ ├── convertToMp4.d.ts.map │ │ ├── iosRecorder.d.ts │ │ └── iosRecorder.d.ts.map │ ├── iosSimulator │ │ ├── convertToMp4.d.ts │ │ ├── convertToMp4.d.ts.map │ │ ├── recorder.d.ts │ │ └── recorder.d.ts.map │ ├── recorder.d.ts │ ├── recorder.d.ts.map │ ├── setOrangeBackground.d.ts │ └── setOrangeBackground.d.ts.map │ ├── video.d.ts │ └── video.d.ts.map └── visualmetrics ├── __init__.py ├── test_data ├── ms_000000.png ├── ms_000920.png ├── ms_001000.png ├── ms_001080.png ├── ms_001200.png ├── ms_001240.png ├── ms_001280.png ├── ms_001360.png ├── ms_001400.png ├── ms_001520.png ├── ms_002040.png ├── ms_002600.png ├── ms_003160.png ├── ms_003720.png ├── ms_004280.png ├── ms_004880.png ├── ms_005440.png └── ms_006000.png ├── test_visualmetrics.py ├── visualmetrics-portable.py └── visualmetrics.py /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !bin 3 | !lib 4 | !package.json 5 | !docker 6 | !vendor 7 | !browsertime 8 | !browserscripts 9 | !browsersupport 10 | !index.js 11 | !visualmetrics -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "node": true, 5 | "es2022": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": "latest", 9 | "sourceType": "module" 10 | }, 11 | "plugins": ["prettier", "unicorn"], 12 | "extends": ["eslint:recommended", "plugin:unicorn/recommended"], 13 | "rules": { 14 | "prettier/prettier": [ 15 | "error", 16 | { 17 | "singleQuote": true, 18 | "trailingComma": "none", 19 | "arrowParens": "avoid", 20 | "embeddedLanguageFormatting": "off" 21 | } 22 | ], 23 | "no-extra-semi": "off", 24 | "no-mixed-spaces-and-tabs": "off", 25 | "no-unexpected-multiline": "off", 26 | "no-return-await": "error", 27 | "require-atomic-updates": "off", 28 | "unicorn/filename-case": "off", 29 | "unicorn/prevent-abbreviations": "off" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | Browsertime needs help from the users so please contribute! 3 | 4 | ## Making changes 5 | If you want help out, that's great! Check the issue list and see if there's something there you want to do and drop Peter a note so we know and we can help you get into the project. 6 | - Create a new branch for your change 7 | - Make commits of logical units and write an informative message 8 | - Make sure you have added the tests needed for your change 9 | - Run all the tests to make sure you haven't broken anything else (it happens to the best!) 10 | 11 | ## Add a defect 12 | First, check the defect/bug list https://github.com/sitespeedio/browsertime/issues?q=is%3Aissue+is%3Aopen+label%3Abug to make sure that it hasn't been filed yet. 13 | 14 | If you find a defect, please file a bug report and follow the instructions in https://www.sitespeed.io/documentation/sitespeed.io/bug-report/ 15 | 16 | If you have the skills & the time, it is perfect if you send a pull request with a fix, that helps me alot! 17 | 18 | ## Add a change request/new functionality request 19 | If you have an idea or something that you want browsertim to handle, add an issue and lets discuss it there. Ideas/changes/requests are very very welcome! 20 | 21 | 22 | Thanks for your support & time! 23 | 24 | Peter 25 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [soulgalore] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: sitespeedio 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_IMPROVEMENT.yml: -------------------------------------------------------------------------------- 1 | name: New feature or improvement 2 | description: Suggest a new feature or something that can be improved 3 | labels: [feature, improvement] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Suggest a new feature or something that can be improved 9 | - type: textarea 10 | id: your-question 11 | attributes: 12 | label: Feature/improvement 13 | description: You can also disuss new features/improvements in the [sitespeed.io Slack channel](https://sitespeedio.herokuapp.com/). 14 | validations: 15 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/QUESTION.yml: -------------------------------------------------------------------------------- 1 | name: Question 2 | description: Ask a question about Browsertime 3 | labels: [question] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Ask a question about Browsertime 9 | - type: textarea 10 | id: your-question 11 | attributes: 12 | label: Your question 13 | description: Please double check that this question hasn't already answered in [old GitHub issues](https://github.com/sitespeedio/sitespeed.io/issues?q=is%3Aissue+is%3Aclosed). You can also ask questions in the [sitespeed.io Slack channel](https://sitespeedio.herokuapp.com/). 14 | validations: 15 | required: true -------------------------------------------------------------------------------- /.github/workflows/browser-beta.yml: -------------------------------------------------------------------------------- 1 | name: Test Firefox/Chrome beta 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: '22.x' 18 | - name: Install Firefox beta 19 | uses: browser-actions/setup-firefox@latest 20 | with: 21 | firefox-version: 'latest-beta' 22 | - name: Install Chrome beta 23 | uses: browser-actions/setup-chrome@latest 24 | with: 25 | chrome-version: beta 26 | - name: Install Browsertime 27 | run: npm ci 28 | - name: Install dependencies 29 | run: | 30 | sudo apt-get install net-tools -y 31 | sudo snap install ffmpeg 32 | sudo snap alias ffmpeg.ffprobe ffprobe 33 | python -m pip install --upgrade --user pip 34 | python -m pip install --upgrade --user setuptools==70.0.0 35 | python -m pip install --user pyssim 36 | python -m pip --version 37 | python -m pip show Pillow 38 | python -m pip show pyssim 39 | python -m pip install virtualenv 40 | sudo modprobe ifb numifbs=1 41 | - name: Browser versions 42 | run: | 43 | chrome --version 44 | firefox --version 45 | - name: Test Chrome Beta 46 | run: ./bin/browsertime.js -b chrome -n 1 https://www.sitespeed.io --connectivity.profile cable --connectivity.engine throttle --xvfb --chrome.binaryPath $(which chrome) 47 | - name: Test Firefox Beta 48 | run: ./bin/browsertime.js -b firefox -n 1 https://www.sitespeed.io --connectivity.profile cable --connectivity.engine throttle --xvfb 49 | -------------------------------------------------------------------------------- /.github/workflows/browser-dev.yml: -------------------------------------------------------------------------------- 1 | name: Test Firefox/Chrome dev 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Use Node.js 15 | uses: actions/setup-node@v4 16 | with: 17 | node-version: '22.x' 18 | - name: Install Firefox latest dev edition 19 | uses: browser-actions/setup-firefox@latest 20 | with: 21 | firefox-version: 'latest-devedition' 22 | - name: Install Chrome dev 23 | uses: browser-actions/setup-chrome@latest 24 | with: 25 | chrome-version: dev 26 | - name: Install Browsertime 27 | run: npm ci 28 | - name: Install dependencies 29 | run: | 30 | sudo apt-get install net-tools -y 31 | sudo snap install ffmpeg 32 | sudo snap alias ffmpeg.ffprobe ffprobe 33 | python -m pip install --upgrade --user pip 34 | python -m pip install --upgrade --user setuptools==70.0.0 35 | python -m pip install --user pyssim OpenCV-Python Numpy 36 | python -m pip --version 37 | python -m pip show Pillow 38 | python -m pip show pyssim 39 | python -m pip install virtualenv 40 | sudo modprobe ifb numifbs=1 41 | - name: Browser versions 42 | run: | 43 | chrome --version 44 | firefox --version 45 | - name: Test Chrome dev 46 | run: ./bin/browsertime.js -b chrome -n 1 https://www.sitespeed.io --connectivity.profile cable --connectivity.engine throttle --xvfb --chrome.binaryPath $(which chrome) 47 | - name: Test Firefox dev 48 | run: ./bin/browsertime.js -b firefox -n 1 https://www.sitespeed.io --connectivity.profile cable --connectivity.engine throttle --xvfb -------------------------------------------------------------------------------- /.github/workflows/building-docker.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker container on new tag 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | jobs: 7 | docker: 8 | runs-on: ubuntu-22.04 9 | steps: 10 | - 11 | name: Checkout 12 | uses: actions/checkout@v4 13 | - 14 | name: Set up QEMU 15 | uses: docker/setup-qemu-action@v3 16 | - 17 | name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v3 19 | - 20 | name: Login to DockerHub 21 | uses: docker/login-action@v3 22 | with: 23 | username: ${{ secrets.DOCKERHUB_USERNAME }} 24 | password: ${{ secrets.DOCKERHUB_TOKEN }} 25 | - 26 | name: Get the tag 27 | id: tag 28 | uses: dawidd6/action-get-tag@v1 29 | with: 30 | strip_v: true 31 | - 32 | name: Build and push 33 | uses: docker/build-push-action@v5 34 | with: 35 | platforms: linux/amd64,linux/arm64 36 | push: true 37 | provenance: false 38 | tags: sitespeedio/browsertime:${{steps.tag.outputs.tag}},sitespeedio/browsertime:latest -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Run Docker 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-22.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Build the container 15 | run: | 16 | docker buildx install 17 | docker buildx build --load --platform linux/amd64 -t sitespeedio/browsertime . 18 | - name: Test Chrome 19 | run: docker run --rm sitespeedio/browsertime https://www.sitespeed.io -n 1 -b chrome 20 | - name: Test Chrome with preWarmServer 21 | run: docker run --rm sitespeedio/browsertime https://www.sitespeed.io -n 1 -b chrome --preWarmServer 22 | - name: Test Firefox 23 | run: docker run --rm sitespeedio/browsertime https://www.sitespeed.io -n 1 -b firefox 24 | - name: Test Edge 25 | run: docker run --rm sitespeedio/browsertime https://www.sitespeed.io -n 1 -b edge 26 | - name: Test WebPageReplay 27 | run: docker run --cap-add=NET_ADMIN --rm -e REPLAY=true -e LATENCY=100 sitespeedio/browsertime https://www.sitespeed.io -n 1 -b chrome 28 | - name: Test Chrome with multi pages 29 | run: docker run --rm -v "$(pwd)":/browsertime sitespeedio/browsertime test/data/navigationscript/simple.cjs -n 1 -b chrome 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-22.04 12 | strategy: 13 | matrix: 14 | node-version: [22.x] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - name: Install Browsertime 22 | run: npm ci 23 | - name: Lint 24 | run: npm run lint -------------------------------------------------------------------------------- /.github/workflows/mac.yml: -------------------------------------------------------------------------------- 1 | name: OSX Chrome, Firefox and Edge 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: macos-13 12 | timeout-minutes: 30 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '22.x' 19 | - name: Install browsertime 20 | run: npm ci 21 | - name: Install python 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.11' 25 | - name: Install dependencies 26 | run: | 27 | brew update 28 | brew install google-chrome 29 | brew install --cask firefox 30 | brew install --cask microsoft-edge 31 | - name: Test Chrome 32 | run: ./bin/browsertime.js -b chrome -n 1 https://www.sitespeed.io/ 33 | - name: Test Firefox 34 | run: ./bin/browsertime.js -b firefox -n 1 https://www.sitespeed.io/ 35 | - name: Test Edge 36 | run: ./bin/browsertime.js -b edge -n 1 https://www.sitespeed.io/ 37 | -------------------------------------------------------------------------------- /.github/workflows/unittests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-22.04 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | browser: ['chrome', 'firefox'] 16 | node-version: [20.x, 22.x, 24.x,] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - name: Install latest Chrome 24 | run: | 25 | wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add - 26 | sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' 27 | sudo apt-get update 28 | sudo apt-get --only-upgrade install google-chrome-stable 29 | google-chrome --version 30 | - name: Install Firefox 31 | uses: browser-actions/setup-firefox@latest 32 | - name: Install Browsertime 33 | run: npm ci 34 | - name: Run unit tests 35 | run: BROWSER=${{ matrix.browser }} npm test 36 | #uses: GabrielBB/xvfb-action@v1.6 37 | #with: 38 | # run: BROWSER=${{ matrix.browser }} npm test 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-debug.log 4 | chromedriver.log 5 | doc/sequence-diagrams/*.png 6 | *.log 7 | *.iml 8 | .vscode 9 | /browsertime-results/ 10 | .tern-project 11 | .Python 12 | .tox/ 13 | bin/activate* 14 | bin/easy_install* 15 | bin/pip* 16 | bin/python* 17 | bin/tox* 18 | bin/virtualenv 19 | bin/wheel 20 | browsertime.egg-info/ 21 | *.pyc 22 | include/ 23 | lib/python* 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM sitespeedio/webbrowsers:chrome-137.0-firefox-139.0-edge-137.0 2 | 3 | ARG TARGETPLATFORM=linux/amd64 4 | 5 | ENV BROWSERTIME_XVFB=true 6 | ENV BROWSERTIME_CONNECTIVITY__ENGINE=external 7 | ENV BROWSERTIME_DOCKER=true 8 | 9 | COPY docker/webpagereplay/$TARGETPLATFORM/wpr /usr/local/bin/ 10 | COPY docker/webpagereplay/wpr_cert.pem /webpagereplay/certs/ 11 | COPY docker/webpagereplay/wpr_key.pem /webpagereplay/certs/ 12 | COPY docker/webpagereplay/deterministic.js /webpagereplay/scripts/deterministic.js 13 | COPY docker/webpagereplay/LICENSE /webpagereplay/ 14 | 15 | RUN sudo apt-get update && DEBIAN_FRONTEND=noninteractive sudo apt-get install libnss3-tools \ 16 | net-tools tcpdump -y && \ 17 | mkdir -p $HOME/.pki/nssdb && \ 18 | certutil -d $HOME/.pki/nssdb -N 19 | 20 | ENV PATH="/usr/local/bin:${PATH}" 21 | 22 | RUN wpr installroot --https_cert_file /webpagereplay/certs/wpr_cert.pem --https_key_file /webpagereplay/certs/wpr_key.pem 23 | 24 | RUN mkdir -p /usr/src/app 25 | WORKDIR /usr/src/app 26 | 27 | VOLUME /browsertime 28 | 29 | COPY package.* /usr/src/app/ 30 | RUN npm install --production 31 | COPY . /usr/src/app 32 | 33 | ## This is to avoid click the OK button 34 | RUN mkdir -m 0750 /root/.android 35 | ADD docker/adb/insecure_shared_adbkey /root/.android/adbkey 36 | ADD docker/adb/insecure_shared_adbkey.pub /root/.android/adbkey.pub 37 | 38 | WORKDIR /browsertime 39 | 40 | COPY docker/scripts/start.sh /start.sh 41 | 42 | # Allow all users to run commands needed by sitespeedio/throttle via sudo 43 | # See https://github.com/sitespeedio/throttle/blob/main/lib/tc.js 44 | RUN echo 'ALL ALL=NOPASSWD: /usr/sbin/tc, /usr/sbin/route, /usr/sbin/ip, /usr/sbin/tcpdump ' > /etc/sudoers.d/tc 45 | 46 | ENTRYPOINT ["/start.sh"] 47 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | The roadmap is a tentative plan for the core development team. Things change constantly as pull requests come in and priorities change, but it will give you an idea of our current vision and plan. 4 | 5 | The roadmap for the rest of 2021 is to refactor Visual Metrics and make a more solid way of running Safari on iOS. 6 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Get Browsertime help 2 | ------------------ 3 | First, check the official [Browsertime/sitespeed.io documentation](https://www.sitespeed.io/documentation/). 4 | 5 | If you require further help or support then check [new and old issues on GitHub](https://github.com/sitespeedio/browsertime/issues) or join the [sitespeed.io Slack](https://join.slack.com/t/sitespeedio/shared_invite/zt-296jzr7qs-d6DId2KpEnMPJSQ8_R~WFw). 6 | 7 | **Please note:** 8 | - The Browsertime project uses GitHub for tracking bugs and feature requests. -------------------------------------------------------------------------------- /bin/browsertimeWebPageReplay.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import merge from 'lodash.merge'; 3 | import yargs from 'yargs'; 4 | import { hideBin } from 'yargs/helpers'; 5 | 6 | import { Engine } from '../lib/core/engine/index.js'; 7 | import { configure as logging } from '../lib/support/logging.js'; 8 | 9 | async function runBrowsertime() { 10 | let yargsInstance = yargs(hideBin(process.argv)); 11 | let parsed = yargsInstance 12 | .env('BROWSERTIME') 13 | .require(1, 'url') 14 | .option('browser', { 15 | alias: 'b', 16 | default: 'chrome', 17 | choices: ['chrome', 'firefox', 'edge'], 18 | describe: 'Specify browser' 19 | }) 20 | .option('connectivity.latency', { 21 | default: undefined, 22 | group: 'connectivity' 23 | }) 24 | .count('verbose') 25 | .alias('v', 'verbose'); 26 | 27 | const defaultConfig = { 28 | iterations: 1, 29 | connectivity: { 30 | downstreamKbps: undefined, 31 | upstreamKbps: undefined, 32 | latency: undefined, 33 | engine: 'external', 34 | localhost: true 35 | }, 36 | delay: 0, 37 | video: false, 38 | visualMetrics: false, 39 | resultDir: '/tmp/browsertime', 40 | screenshotParams: { 41 | type: 'jpg' 42 | }, 43 | chrome: { 44 | ignoreCertificateErrors: true 45 | } 46 | }; 47 | 48 | const btOptions = merge({}, parsed.argv, defaultConfig); 49 | logging(parsed.argv); 50 | 51 | const engine = new Engine(btOptions); 52 | try { 53 | await engine.start(); 54 | const result = await engine.runMultiple(parsed.argv._); 55 | for (let errors of result.errors) { 56 | if (errors.length > 0) { 57 | process.exitCode = 1; 58 | } 59 | } 60 | } finally { 61 | await engine.stop(); 62 | process.exit(); 63 | } 64 | } 65 | 66 | await runBrowsertime(); 67 | -------------------------------------------------------------------------------- /browserscripts/browser/cpuBenchmark.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // simple CPU benchmarking 3 | // following https://github.com/wikimedia/mediawiki-extensions-NavigationTiming/blob/master/modules/ext.navigationTiming.js 4 | // except not in worker to avoid CSP issues. 5 | const amount = 100000000; 6 | const startTime = performance.now(); 7 | for ( let i = amount; i > 0; i-- ) { 8 | // empty 9 | } 10 | return Math.round( performance.now() - startTime ); 11 | })(); -------------------------------------------------------------------------------- /browserscripts/browser/userAgent.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | return navigator.userAgent; 3 | })(); 4 | -------------------------------------------------------------------------------- /browserscripts/browser/windowSize.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const w = window, 3 | d = document, 4 | e = d.documentElement, 5 | g = d.getElementsByTagName('body')[0], 6 | x = w.innerWidth || e.clientWidth || g.clientWidth, 7 | y = w.innerHeight || e.clientHeight || g.clientHeight; 8 | return x + 'x' + y; 9 | })(); 10 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/cumulativeLayoutShift.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const supported = PerformanceObserver.supportedEntryTypes; 3 | if (!supported || supported.indexOf('layout-shift') === -1) { 4 | return; 5 | } 6 | // See https://web.dev/layout-instability-api 7 | // https://github.com/mmocny/web-vitals/wiki/Snippets-for-LSN-using-PerformanceObserver#max-session-gap1s-limit5s 8 | let max = 0; 9 | let curr = 0; 10 | let firstTs = Number.NEGATIVE_INFINITY; 11 | let prevTs = Number.NEGATIVE_INFINITY; 12 | const observer = new PerformanceObserver(list => {}); 13 | observer.observe({ type: 'layout-shift', buffered: true }); 14 | const list = observer.takeRecords(); 15 | for (let entry of list) { 16 | if (entry.hadRecentInput) { 17 | continue; 18 | } 19 | if (entry.startTime - firstTs > 5000 || entry.startTime - prevTs > 1000) { 20 | firstTs = entry.startTime; 21 | curr = 0; 22 | } 23 | prevTs = entry.startTime; 24 | curr += entry.value; 25 | max = Math.max(max, curr); 26 | } 27 | return max; 28 | })(); 29 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/documentHeight.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function docHeight(doc) { 3 | const body = doc.body, 4 | docelem = doc.documentElement; 5 | return Math.max( 6 | body.scrollHeight, 7 | body.offsetHeight, 8 | docelem.clientHeight, 9 | docelem.scrollHeight, 10 | docelem.offsetHeight 11 | ); 12 | } 13 | 14 | return docHeight(document); 15 | })(); 16 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/documentSize.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const t = window.performance.getEntriesByType('navigation')[0]; 3 | // Safari doesnt support getEntriesByType('navigation') 4 | if (t) { 5 | return { 6 | decodedBodySize: t.decodedBodySize, 7 | encodedBodySize: t.encodedBodySize, 8 | transferSize: t.transferSize 9 | }; 10 | } else { 11 | return; 12 | } 13 | })(); 14 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/documentTitle.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // https://github.com/sitespeedio/browsertime/issues/979 3 | if (typeof document.title === "string") { 4 | return document.title; 5 | } else { 6 | const titles = document.getElementsByTagName("title"); 7 | if (titles.length > 0) { 8 | return titles[0].innerHTML; 9 | } else return ""; 10 | } 11 | })(); 12 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/documentWidth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function docWidth(doc) { 3 | const body = doc.body, 4 | docelem = doc.documentElement; 5 | return Math.max( 6 | body.scrollWidth, 7 | body.offsetWidth, 8 | docelem.clientWidth, 9 | docelem.scrollWidth, 10 | docelem.offsetWidth 11 | ); 12 | } 13 | 14 | return docWidth(document); 15 | })(); 16 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/domElements.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | return document.getElementsByTagName("*").length; 4 | })(); 5 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/generator.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | const description = document.querySelector('meta[name="generator"]'); 4 | if (description) { 5 | return description.getAttribute('content'); 6 | } 7 | })(); -------------------------------------------------------------------------------- /browserscripts/pageinfo/navigationStartTime.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (window.performance.timeOrigin) { 3 | return Number(window.performance.timeOrigin.toFixed(0)); 4 | } 5 | if (window.performance.timing.navigationStart) { 6 | return Number(window.performance.timing.navigationStart.toFixed(0)); 7 | } 8 | return undefined; 9 | })(); 10 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/nextHopProtocol.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const t = window.performance.getEntriesByType('navigation')[0]; 3 | // Not supported in Safari 4 | if (t) { 5 | return t.nextHopProtocol; 6 | } 7 | })(); 8 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/resources.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const resources = window.performance.getEntriesByType('resource'); 3 | 4 | let resourceDuration = 0; 5 | let servedFromCache = 0; 6 | let servedFromCacheSupported = false; 7 | for (let i = 0; i < resources.length; i++) { 8 | resourceDuration += resources[i].duration; 9 | if (resources[i].deliveryType !== undefined) { 10 | servedFromCacheSupported = true; 11 | if (resources[i].deliveryType === 'cache') { 12 | servedFromCache++; 13 | } 14 | } 15 | } 16 | 17 | const info = { 18 | count: Number(resources.length), 19 | duration: Number(resourceDuration), 20 | }; 21 | 22 | if (servedFromCacheSupported === true) { 23 | info.servedFromCache = Number(servedFromCache); 24 | } 25 | 26 | return info; 27 | })(); -------------------------------------------------------------------------------- /browserscripts/pageinfo/responsive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | return document.documentElement.scrollWidth <= window.innerWidth; 3 | })(); 4 | -------------------------------------------------------------------------------- /browserscripts/pageinfo/url.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // https://github.com/sitespeedio/browsertime/issues/979#issuecomment-549107350 3 | if (typeof document.URL === "string") { 4 | return document.URL; 5 | } else { 6 | return window.location.href; 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /browserscripts/timings/.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | globals: 4 | window: true 5 | -------------------------------------------------------------------------------- /browserscripts/timings/elementTimings.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const supported = PerformanceObserver.supportedEntryTypes; 3 | if (!supported || supported.indexOf('element') === -1) { 4 | return; 5 | } 6 | const observer = new PerformanceObserver(list => {}); 7 | observer.observe({ type: 'element', buffered: true }); 8 | const entries = observer.takeRecords(); 9 | const elements = {}; 10 | for (let entry of entries) { 11 | // Look out for colliding identifiers and missing identifiers 12 | elements[entry.identifier] = { 13 | duration: entry.duration, 14 | url: entry.url, 15 | loadTime: Number(entry.loadTime.toFixed(0)), 16 | renderTime: Number(entry.renderTime.toFixed(0)), 17 | startTime: Number(entry.startTime.toFixed(0)), 18 | naturalHeight: entry.naturalHeight, 19 | naturalWidth: entry.naturalWidth, 20 | tagName: entry.element ? entry.element.tagName : '' 21 | }; 22 | } 23 | return elements; 24 | })(); 25 | -------------------------------------------------------------------------------- /browserscripts/timings/firstInput.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const supported = PerformanceObserver.supportedEntryTypes; 3 | if (!supported || supported.indexOf("first-input") === -1) { 4 | return; 5 | } 6 | const observer = new PerformanceObserver(list => {}); 7 | observer.observe({ type: "first-input", buffered: true }); 8 | const entries = observer.takeRecords(); 9 | if (entries.length > 0) { 10 | const entry = entries[entries.length - 1]; 11 | return { 12 | duration: entry.duration, 13 | name: entry.name, 14 | processingEnd: Number(entry.processingEnd.toFixed(0)), 15 | processingStart: Number(entry.processingStart.toFixed(0)), 16 | startTime: Number(entry.startTime.toFixed(0)), 17 | delay: Number((entry.processingStart - entry.startTime).toFixed(1)) 18 | }; 19 | } else return; 20 | })(); 21 | -------------------------------------------------------------------------------- /browserscripts/timings/firstPaint.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let p = window.performance, 3 | timing = p.timing, 4 | entries = p.getEntriesByType('paint'); 5 | 6 | if (entries.length > 0) { 7 | for (const entry of entries) { 8 | if (entry.name === 'first-paint') 9 | return Number(entry.startTime.toFixed(0)); 10 | } 11 | } 12 | if (timing.timeToNonBlankPaint) { 13 | return Number( 14 | (timing.timeToNonBlankPaint - timing.navigationStart).toFixed(0) 15 | ); 16 | } 17 | return undefined; 18 | })(); 19 | -------------------------------------------------------------------------------- /browserscripts/timings/loadEventEnd.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (window.performance.getEntriesByType('navigation').length > 0) { 3 | return Number( 4 | window.performance 5 | .getEntriesByType('navigation')[0] 6 | .loadEventEnd.toFixed(0) 7 | ); 8 | } else { 9 | return Number( 10 | window.performance.timing.loadEventEnd - 11 | window.performance.timing.navigationStart 12 | ); 13 | } 14 | })(); 15 | -------------------------------------------------------------------------------- /browserscripts/timings/navigationTiming.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let t = window.performance.getEntriesByType('navigation')[0]; 3 | const d = 0; 4 | return { 5 | connectStart: Number(t.connectStart.toFixed(d)), 6 | domComplete: Number(t.domComplete.toFixed(d)), 7 | domContentLoadedEventEnd: Number(t.domContentLoadedEventEnd.toFixed(d)), 8 | domContentLoadedEventStart: Number( 9 | t.domContentLoadedEventStart.toFixed(d) 10 | ), 11 | domInteractive: Number(t.domInteractive.toFixed(d)), 12 | domainLookupEnd: Number(t.domainLookupEnd.toFixed(d)), 13 | domainLookupStart: Number(t.domainLookupStart.toFixed(d)), 14 | duration: Number(t.duration.toFixed(d)), 15 | fetchStart: Number(t.fetchStart.toFixed(d)), 16 | loadEventEnd: Number(t.loadEventEnd.toFixed(d)), 17 | loadEventStart: Number(t.loadEventStart.toFixed(d)), 18 | redirectEnd: Number(t.redirectEnd.toFixed(d)), 19 | redirectStart: Number(t.redirectStart.toFixed(d)), 20 | requestStart: Number(t.requestStart.toFixed(d)), 21 | responseEnd: Number(t.responseEnd.toFixed(d)), 22 | responseStart: Number(t.responseStart.toFixed(d)), 23 | secureConnectionStart: Number(t.secureConnectionStart.toFixed(d)), 24 | startTime: Number(t.startTime.toFixed(d)), 25 | unloadEventEnd: Number(t.unloadEventEnd.toFixed(d)), 26 | unloadEventStart: Number(t.unloadEventStart.toFixed(d)), 27 | workerStart: Number(t.workerStart.toFixed(d)) 28 | }; 29 | })(); 30 | -------------------------------------------------------------------------------- /browserscripts/timings/pageTimings.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let t = window.performance.getEntriesByType('navigation')[0]; 3 | const d = 0; 4 | return { 5 | domainLookupTime: Number( 6 | (t.domainLookupEnd - t.domainLookupStart).toFixed(d) 7 | ), 8 | redirectionTime: Number((t.redirectEnd - t.redirectStart).toFixed(d)), 9 | serverConnectionTime: Number((t.connectEnd - t.connectStart).toFixed(d)), 10 | serverResponseTime: Number((t.responseEnd - t.requestStart).toFixed(d)), 11 | pageDownloadTime: Number((t.responseEnd - t.responseStart).toFixed(d)), 12 | domInteractiveTime: Number(t.domInteractive.toFixed(d)), 13 | domContentLoadedTime: Number(t.domContentLoadedEventStart.toFixed(d)), 14 | pageLoadTime: Number(t.loadEventStart.toFixed(d)), 15 | frontEndTime: Number((t.loadEventStart - t.responseEnd).toFixed(d)), 16 | backEndTime: Number(t.responseStart.toFixed(d)) 17 | } 18 | })(); 19 | -------------------------------------------------------------------------------- /browserscripts/timings/paintTiming.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | let p = window.performance, 3 | entries, 4 | values = {}; 5 | 6 | entries = p.getEntriesByType('paint'); 7 | 8 | if (entries.length > 0) { 9 | for (const entry of entries) { 10 | values[entry.name] = Number(entry.startTime.toFixed(0)); 11 | } 12 | return values; 13 | } else return undefined; 14 | })(); 15 | -------------------------------------------------------------------------------- /browserscripts/timings/serverTimings.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let t = window.performance.getEntriesByType('navigation')[0]; 3 | const serverTimings = []; 4 | for (let timing of t.serverTiming) { 5 | serverTimings.push({ 6 | name: timing.name, 7 | duration: timing.duration, 8 | description: timing.description 9 | }); 10 | } 11 | return serverTimings; 12 | })(); 13 | -------------------------------------------------------------------------------- /browserscripts/timings/timeToContentfulPaint.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Firefox only timeToContentfulPaint 3 | // need pref to be activated 4 | const timing = window.performance.timing; 5 | if (timing.timeToContentfulPaint) { 6 | return Number( 7 | (timing.timeToContentfulPaint - timing.navigationStart).toFixed(0) 8 | ); 9 | } 10 | else return undefined; 11 | })(); 12 | -------------------------------------------------------------------------------- /browserscripts/timings/timeToDomContentFlushed.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Firefox only timeToDOMContentFlushed 3 | // need pref to be activated 4 | const timing = window.performance.timing; 5 | if (timing.timeToDOMContentFlushed) { 6 | return Number( 7 | (timing.timeToDOMContentFlushed - timing.navigationStart).toFixed(0) 8 | ); 9 | } 10 | else return undefined; 11 | })(); 12 | -------------------------------------------------------------------------------- /browserscripts/timings/timeToFirstInteractive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Firefox only TTFI 3 | // need pref to be activated 4 | // If the "event" has happend, it will return 0 5 | const timing = window.performance.timing; 6 | if (timing.timeToFirstInteractive && timing.timeToFirstInteractive > 0) { 7 | const ttfi = Number( 8 | (timing.timeToFirstInteractive - timing.navigationStart).toFixed(0) 9 | ); 10 | // We have seen cases when TTFI is - 46 years. 11 | if (ttfi < 0) { 12 | return 0; 13 | } else { 14 | return ttfi; 15 | } 16 | } else return undefined; 17 | })(); 18 | -------------------------------------------------------------------------------- /browserscripts/timings/ttfb.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | let t = window.performance.getEntriesByType('navigation')[0]; 3 | if (t) { 4 | return Number(t.responseStart.toFixed(0)); 5 | } else { 6 | return Number(window.performance.timing.responseStart.toFixed(0) - window.performance.timing.navigationStart.toFixed(0)); 7 | } 8 | })(); 9 | -------------------------------------------------------------------------------- /browserscripts/timings/userTimings.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | const measures = []; 3 | const marks = []; 4 | 5 | if (window.performance && window.performance.getEntriesByType) { 6 | const myMarks = Array.prototype.slice.call( 7 | window.performance.getEntriesByType('mark') 8 | ); 9 | 10 | for (const mark of myMarks) { 11 | if (mark.detail && mark.detail.devtools) { 12 | continue; 13 | } else { 14 | marks.push({ 15 | name: mark.name, 16 | startTime: mark.startTime 17 | }); 18 | } 19 | } 20 | 21 | const myMeasures = Array.prototype.slice.call( 22 | window.performance.getEntriesByType('measure') 23 | ); 24 | 25 | for (const measure of myMeasures) { 26 | if (measure.detail && measure.detail.devtools) { 27 | continue; 28 | } else { 29 | measures.push({ 30 | name: measure.name, 31 | duration: measure.duration, 32 | startTime: measure.startTime 33 | }); 34 | } 35 | } 36 | } 37 | 38 | return { 39 | marks: marks, 40 | measures: measures 41 | }; 42 | })(); 43 | -------------------------------------------------------------------------------- /browsertime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/browsertime.png -------------------------------------------------------------------------------- /docker/adb/insecure_shared_adbkey: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCppPEDWVQFScXE 3 | A/Z5KkDP62pbSSYiFOvpK4ixvAGOoeFD/Jw8m0VtxCsjiXylNaFXA10smHi1Hw3n 4 | vMruEYpQVv3kB4ij0NeZXZ+7jg+lkkQErWiWhACxerOjuOTbnoMtqdDZWwyl1Cd8 5 | ob2AWrVPZmTWoHG8Rw5yrUgDDYMgFMZpmywbXBqpSgw9NdzfLPGuH89LfzpE0hIN 6 | tOsIDjC2GG52h7hTBg6YwtSmFIMO/dk3jEob8eBKVaa5sB19m0POd+9E4Hlo2fEs 7 | +JbnluR7y9YKiTuXZDXTtcvZyg+aIreVtfOzpLbPcBUzBVSIYdc2z+Vq74r/f8pk 8 | QfiCwnHNAgMBAAECggEAZtD44ba6HZpgqjRcpYLeVSWxCDKFUhKsCF3CMzZnGzMx 9 | fCsV5gWVRrmmC5vAV8DxT/NR/T1LqzpvCwx5UWCAG8Edj56hSefBQ8pijSHEiezk 10 | HJGc2dyXLvnW9luRGSoxBvPtCE8Ok1LJu9erKqfPS5gbdZk4VYwbTZWIF5GQ71er 11 | bVS3jwKvqkUgGiZvfVTHmCCx7LGg6vpSS7dfAIPF2lBQfzfIN+epvXhSN8v2w8Lm 12 | 6IYqEI9GVBF7uflk+ogn/2gsZRx2nZzKrPJV1XbHebPOtWNOkDlK+zF8IYAz8e8k 13 | eHBD9DrWX/SlKbklMdGxRqWxayvASSgBjQqjykqKAQKBgQDQkhLkZdpHtLIhZeeB 14 | lwNTrLPiGzg4xMWgeFIMkAxb7ZRWAmbD1OL4OGC+brpb0aQqE2xdlvNcgCq84lof 15 | guLcpu6KJ7qdUzERZicdDtph4NGAKS6djhurHAdQsLaTs6wq57zPWOBYymBAV5TE 16 | 7m5d6/JZsNNEPbaPC8pY8ASsHQKBgQDQOMZ7jibT2gI4fxhCMvGJ6q5tSGKOBozZ 17 | FDcgua2KIqgD73XuW+bAIi/4uPzwvdRUk/quI5ZECfMgNzTLKqOOV/OIz7/ZIduO 18 | gWKM7OskF1JY6ihsTwvVgOUj2TtbZzDZdN1fuUk5FYq4QFr2wXQzlHMSczuAAnEL 19 | kU/dT7kNcQKBgQCLAtDUqY3yfNy8pc7G8H+nJVQ/PyUZsQyHB6qn9NpH6vES4kbb 20 | /ufHyMuyINrUl8Vyxb9UIWfSHxpdCgBHQFUz+47BRfl7IhdyIUOwelXTJqR7ZvdK 21 | y4xlXykA/saxau81KX8OM45Tn47HU5g0KTYmIzxDyzcEJJ2oeZND87UpgQKBgQCR 22 | 3eQ19CyBJu19VJPS9Es/Obd9+UKJik8rV70S4OCQr5ySPTOZiqoJGSoQDM+tet5/ 23 | bbckPOvsuCeo/uOuHC297yE9S4RzgQOFPmCipupHO0tF3Kv6zBlXNVfQmEK70ntn 24 | KzZV88A3DD9Euli/GmDkLW+7khwxngRBfUe8mzfhEQKBgQCSZDQj+luSV3NYoQN8 25 | Uhoz/dpWA1mFYSsRj61K/o0kfscPGoEEhpwJQFIJZBtVaUQlR74nhPBqfk2pHzl/ 26 | LYiHVMKzJ4LZ+60GYay6v2oaL0gXDSc/OiPV1HbvZ6RU4GuuI+5UyeBCyADxN9yE 27 | Ql9mSkxC8xWUlCBzS8Nojdsybw== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/adb/insecure_shared_adbkey.pub: -------------------------------------------------------------------------------- 1 | QAAAAPsciFrNccKC+EFkyn//iu9q5c8212GIVAUzFXDPtqSz87WVtyKaD8rZy7XTNWSXO4kK1st75Jbnlvgs8dloeeBE73fOQ5t9HbC5plVK4PEbSow32f0OgxSm1MKYDgZTuId2bhi2MA4I67QNEtJEOn9Lzx+u8Szf3DU9DEqpGlwbLJtpxhQggw0DSK1yDke8caDWZGZPtVqAvaF8J9SlDFvZ0Kktg57b5Lijs3qxAISWaK0ERJKlD467n12Z19CjiAfk/VZQihHuyrznDR+1eJgsXQNXoTWlfIkjK8RtRZs8nPxD4aGOAbyxiCvp6xQiJklbauvPQCp59gPExUkFVFkD8aSp84VBRBR0aoMw7zpCFsWQeOzuXAdHpWhJDRmaY3+Z835gOd4g4Qo39noyy0bsMEQOlDZNnXE633J5kdm7cqA7R5gFGI421i1wOKzMuHilolYZf0chRDFZXxPgDNunlQecGWHW1upm+O+/6GcXAsiOZcjthcE95FW3G9Psu/c/RhAPCB1dy8yyJGKkNk7n8yXAJbIYsSnhoM39eIcSGbtxxXB27V22n+KuzQRq7FubtYbqO/I+W0YzojuoWLrflwtmtcDPGZgI+pcAhXHpF4zvxOsi3HYemZ4dre3ELjQQ3vGwYRCJ23pqya8ifQxMrzZs7YFT91I/0V03BEquRpYYNgEAAQA= insecure_shared_adbkey -------------------------------------------------------------------------------- /docker/webpagereplay/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 The Chromium Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | * Neither the name of catapult nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /docker/webpagereplay/linux/amd64/wpr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/docker/webpagereplay/linux/amd64/wpr -------------------------------------------------------------------------------- /docker/webpagereplay/linux/arm64/wpr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/docker/webpagereplay/linux/arm64/wpr -------------------------------------------------------------------------------- /docker/webpagereplay/wpr_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICDDCCAXUCFDpoMKg9jdABK5CDWJvb9ZPgiY0tMA0GCSqGSIb3DQEBCwUAMEUx 3 | CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl 4 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMjAwNjE5MTgyODQ4WhcNMjMwMzE2MTgy 5 | ODQ4WjBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UE 6 | CgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GN 7 | ADCBiQKBgQCyzqGNRDTe/HTLzt97UhJXXWmjxVeKKFySmwqlrBLq5CiY7aB8Z64u 8 | eK4DTi0lODvGkMrVhPc6uhIv3Kiu3ZelKmYmbcJOB1awS+/OnFSpYKT5IXJxvhDH 9 | Q8cvz8jA9/IzbUzTCS4sG8YmajKN742f+PtVy3TYrAFkwy2lnl8ZmwIDAQABMA0G 10 | CSqGSIb3DQEBCwUAA4GBAD4AXGQ7eaIdnnI9SJMkgEJIpJMJNOl4kN/kESMmJE9G 11 | GFJ4oiP6VzXPVMTW4xeqmuIIqX8ZovO8SA+C2e1yqcz6X8WN7EDD0UJVGiru9yaT 12 | C7AbTTK8NsKxKKGzGF6qnnnwtKc8ZIypw4/00yw8vZOwMwLOV+ffvkUX9uW9Ws8z 13 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /docker/webpagereplay/wpr_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALLOoY1ENN78dMvO 3 | 33tSElddaaPFV4ooXJKbCqWsEurkKJjtoHxnri54rgNOLSU4O8aQytWE9zq6Ei/c 4 | qK7dl6UqZiZtwk4HVrBL786cVKlgpPkhcnG+EMdDxy/PyMD38jNtTNMJLiwbxiZq 5 | Mo3vjZ/4+1XLdNisAWTDLaWeXxmbAgMBAAECgYAadwLqScIZjvwqfkANnKQiUi0k 6 | lDzUsgyhllkJFGLoaUSo/eLXBvF851e6HYQJEj2msh+TYs7E3m16sAo3d4zOIdnz 7 | VwOF0SVuUveqJz6K1/k6nPxck+dPj8Mi+gBm3Fd0+0wcozjWaxhx3f462HCUb6b+ 8 | ZpJRBsbyvzu6rn7iQQJBAOlWhtfL8r9+Kl0vxRD1XukaJwlxPv24JhfKOU4z8WlJ 9 | WX7Wr8ws+xKS+CtfFnjkf/iFJPpTb8jxpQyWMJzYZIkCQQDELE5hGnBFVQArMAOp 10 | VbwYordTrVY3AagO4tDJ6T3a7GEXE28ol16/i02+4FLd65vubL21IuX0exH/eRvZ 11 | Q4wDAkEAub/qyiEOFkjOWq5rd0uNiY0LJGYlWf7dPDT8l3ecJ09/0gv/mE76c9fR 12 | fV1N22EzSlhbjncbVuCenj11Z3aP2QJAILtfzJXzu63GHG6jfcKfYuDrg9u9Mepl 13 | 1y4DNl1jg77DKG2Gs5gmKAGfVETrrrmcR/j+4lVTVyqdwym6+tJpbwJBAN3vixxc 14 | 5N9pUMDfFnHrx/x9QPd0JgSAT21KSIB+PndlbD7QO6nwFhQNNcTYt2D4VWPVo1vg 15 | lOraHyFakb7NqEA= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import prettier from 'eslint-plugin-prettier'; 2 | import unicorn from 'eslint-plugin-unicorn'; 3 | import globals from 'globals'; 4 | import path from 'node:path'; 5 | import { fileURLToPath } from 'node:url'; 6 | import js from '@eslint/js'; 7 | import { FlatCompat } from '@eslint/eslintrc'; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all 15 | }); 16 | 17 | export default [ 18 | { 19 | ignores: [ 20 | 'browserscripts/*', 21 | 'tools/*', 22 | 'test/data/*', 23 | 'docker/webpagereplay/*', 24 | 'vendor/*' 25 | ] 26 | }, 27 | ...compat.extends('eslint:recommended', 'plugin:unicorn/recommended'), 28 | { 29 | plugins: { 30 | prettier, 31 | unicorn 32 | }, 33 | 34 | languageOptions: { 35 | globals: { 36 | ...globals.node 37 | }, 38 | 39 | ecmaVersion: 'latest', 40 | sourceType: 'module' 41 | }, 42 | 43 | rules: { 44 | 'prettier/prettier': [ 45 | 'error', 46 | { 47 | singleQuote: true, 48 | trailingComma: 'none', 49 | arrowParens: 'avoid', 50 | embeddedLanguageFormatting: 'off' 51 | } 52 | ], 53 | 54 | 'no-extra-semi': 'off', 55 | 'no-mixed-spaces-and-tabs': 'off', 56 | 'no-unexpected-multiline': 'off', 57 | 'no-return-await': 'error', 58 | 'require-atomic-updates': 'off', 59 | 'unicorn/filename-case': 'off', 60 | 'unicorn/prevent-abbreviations': 'off' 61 | } 62 | } 63 | ]; 64 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { 2 | defaultScriptCategories, 3 | allScriptCategories, 4 | getScriptsForCategories, 5 | findAndParseScripts 6 | } from './lib/support/browserScript.js'; 7 | 8 | export const browserScripts = { 9 | defaultScriptCategories, 10 | allScriptCategories, 11 | getScriptsForCategories, 12 | findAndParseScripts 13 | }; 14 | 15 | export { Engine as BrowsertimeEngine } from './lib/core/engine/index.js'; 16 | export { configure as configureLogging } from './lib/support/logging.js'; 17 | 18 | export { Commands as BrowsertimeCommands } from './lib/core/engine/commands.js'; 19 | export { Context as BrowsertimeContext } from './lib/core/engine/context.js'; 20 | -------------------------------------------------------------------------------- /jsdoc/README.md: -------------------------------------------------------------------------------- 1 | # Scripting 2 | 3 | The user journey 4 | 5 | Welcome to the powerful world of scripting with sitespeed.io! This feature unlocks the potential to simulate real user journeys, measure performance, and gather detailed metrics by interacting with web pages through custom scripts. Whether you're looking to analyze simple page loads or complex user interactions, our scripting functionality offers the tools you need. 6 | 7 | 8 | ## Key Features 9 | 10 | * **User Journey Simulation**: Script entire user flows, from navigation to clicks and form submissions, to capture a realistic user experience. 11 | * **Performance Metrics Collection**: Gather crucial data like load times, visual metrics, and more, for each step of your user journey. 12 | * **Flexible Scripting Language**: Write scripts in NodeJS, using familiar JavaScript syntax and robust libraries. 13 | 14 | ## Getting Started 15 | 16 | Dive into scripting with our [tutorials](Tutorial-01-Introduction.html), [examples](tutorial-09-Examples.html), and [documentation for all commands](Commands.html). 17 | 18 | If you are new to NodeJS and want to understand async/await you should read [this post about async/await](https://blog.postman.com/understanding-async-await-in-node-js/). -------------------------------------------------------------------------------- /jsdoc/tutorials/12-Bidi.md: -------------------------------------------------------------------------------- 1 | Send messages to the browser using the [BiDirectional WebDriver Protocol (Bidi)](https://w3c.github.io/webdriver-bidi/). This works in Firefox today and later more browsers. You can send, send and get and listen on events. This is a super powerful feature that enables you to do a lot. 2 | 3 | There's no user friendly documentation right now for Bidi. 4 | 5 | ### Sending a command 6 | 7 | Here’s an example of sending a command that injects JavaScript that runs on every new document. 8 | 9 | ```javascript 10 | /** 11 | * @param {import('browsertime').BrowsertimeContext} context 12 | * @param {import('browsertime').BrowsertimeCommands} commands 13 | */ 14 | export default async function (context, commands) { 15 | const params = { 16 | method: 'script.addPreloadScript', 17 | params: { 18 | functionDeclaration: "function() {alert('hello');}" 19 | } 20 | }; 21 | await commands.bidi.send(params); 22 | await commands.measure.start('https://www.sitespeed.io'); 23 | } 24 | ``` 25 | 26 | ### Subscribe and unsubscribe to events 27 | 28 | You need to subscribe to the event types that you are interested in with `commands.bidi.subscribe('messageType');` and unsubscribe when you are done. 29 | 30 | ### Listen on events 31 | 32 | When you subscribe on an event you want to do something when the events happen. 33 | 34 | ```javascript 35 | /** 36 | * @param {import('browsertime').BrowsertimeContext} context 37 | * @param {import('browsertime').BrowsertimeCommands} commands 38 | */ 39 | export default async function (context, commands) { 40 | // We want to to do something on all the requests that are sent 41 | await commands.bidi.subscribe('network.beforeRequestSent'); 42 | 43 | await commands.bidi.onMessage(function (event) { 44 | const myEvent = JSON.parse(Buffer.from(event.toString())); 45 | console.log(myEvent); 46 | }); 47 | 48 | await commands.navigate('https://www.sitespeed.io'); 49 | } 50 | ``` -------------------------------------------------------------------------------- /jsdoc/tutorials/13-Android.md: -------------------------------------------------------------------------------- 1 | Testing on an Android device should work the same way as testing on desktop, as long as you setup your device following [the instructions](https://www.sitespeed.io/documentation/sitespeed.io/mobile-phones/#prerequisites). 2 | 3 | ## Run shell command 4 | If you run your tests on an Android phone you can interact with your phone through the shell. 5 | 6 | ```javascript 7 | /** 8 | * @param {import('browsertime').BrowsertimeContext} context 9 | * @param {import('browsertime').BrowsertimeCommands} commands 10 | */ 11 | export default async function (context, commands) { 12 | // Get the temperature from the phone 13 | const temperature = await commands.android.shell("dumpsys battery | grep temperature | grep -Eo '[0-9]{1,3}'"); 14 | context.log.info('The battery temperature is %s', temperature/10); 15 | // Start the test 16 | return commands.measure.start( 17 | 'https://www.sitespeed.io' 18 | ); 19 | }; 20 | ``` 21 | 22 | ## Run shell command as root 23 | If you rooted your device and want to run as root. 24 | 25 | ```javascript 26 | /** 27 | * @param {import('browsertime').BrowsertimeContext} context 28 | * @param {import('browsertime').BrowsertimeCommands} commands 29 | */ 30 | export default async function (context, commands) { 31 | // Get the temperature from the phone 32 | const temperature = await commands.android.shellAsRoot("dumpsys battery | grep temperature | grep -Eo '[0-9]{1,3}'"); 33 | context.log.info('The battery temperature is %s', temperature/10); 34 | // Start the test 35 | return commands.measure.start( 36 | 'https://www.sitespeed.io' 37 | ); 38 | }; 39 | ``` -------------------------------------------------------------------------------- /jsdoc/tutorials/tutorials.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "01-Introduction": { 4 | "title": "Introduction" 5 | }, 6 | "02-Running-Scripts": { 7 | "title": "Running and managing scripts" 8 | }, 9 | "03-Measurement-Commands": { 10 | "title": "Measure" 11 | }, 12 | "04-Interact-with-the-page": { 13 | "title": "Interact with the page" 14 | }, 15 | "05-Interact-Browser": { 16 | "title": "Interact with the browser" 17 | }, 18 | "06-Error-handling": { 19 | "title": "Error handling" 20 | }, 21 | "07-Debugging-Scripts": { 22 | "title": "Debugging scripts" 23 | }, 24 | "08-Setting-Up-IntelliSense": { 25 | "title": "Code completion and IntelliSense" 26 | }, 27 | "09-Examples": { 28 | "title": "Examples" 29 | }, 30 | "10-Selenium": { 31 | "title": "Running Selenium code" 32 | }, 33 | "11-Chrome-Devtools-Protocol": { 34 | "title": "Chrome Devtools Protocol (CDP)" 35 | }, 36 | "12-Bidi": { 37 | "title": "Using Bidi" 38 | }, 39 | "13-Android": { 40 | "title": "Android devices" 41 | }, 42 | "18-Tips-and-tricks": { 43 | "title": "Tips and tricks" 44 | } 45 | } -------------------------------------------------------------------------------- /lib/chrome/networkManager.js: -------------------------------------------------------------------------------- 1 | import { getLogger } from '@sitespeed.io/log'; 2 | import { getProperty } from '../support/util.js'; 3 | 4 | const log = getLogger('browsertime.chrome.network'); 5 | 6 | export class NetworkManager { 7 | constructor(cdpClient, options) { 8 | this.maxTimeout = getProperty( 9 | options, 10 | 'timeouts.pageCompleteCheck', 11 | 30_000 12 | ); 13 | this.idleTime = getProperty(options, 'timeouts.networkIdle', 5000); 14 | 15 | this.cdp = cdpClient.getRawClient(); 16 | this.inflight = 0; 17 | this.lastRequestTimestamp; 18 | this.lastResponseTimestamp; 19 | 20 | this.cdp.Network.requestWillBeSent(() => { 21 | this.inflight++; 22 | this.lastRequestTimestamp = Date.now(); 23 | }); 24 | 25 | this.cdp.Network.loadingFinished(() => { 26 | this.inflight--; 27 | this.lastResponseTimestamp = Date.now(); 28 | }); 29 | 30 | this.cdp.Network.loadingFailed(() => { 31 | this.inflight--; 32 | this.lastResponseTimestamp = Date.now(); 33 | }); 34 | } 35 | 36 | async waitForNetworkIdle() { 37 | const startTime = Date.now(); 38 | 39 | while (true) { 40 | const now = Date.now(); 41 | const sinceLastResponseRequest = 42 | now - Math.max(this.lastResponseTimestamp, this.lastRequestTimestamp); 43 | const sinceStart = now - startTime; 44 | 45 | if (sinceLastResponseRequest >= this.idleTime) { 46 | if (this.inflight > 0) { 47 | log.info( 48 | 'Idle time without any request/responses. Inflight requests:' + 49 | this.inflight 50 | ); 51 | } 52 | break; 53 | } 54 | 55 | if (sinceStart >= this.maxTimeout) { 56 | log.info( 57 | 'Timeout waiting for network. Inflight requests:' + this.inflight 58 | ); 59 | break; 60 | } 61 | 62 | await new Promise(r => setTimeout(r, 200)); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/chrome/settings/chromeAndroidOptions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Options when we run Chrome on Android 3 | * see https://github.com/GoogleChrome/chrome-launcher/blob/master/docs/chrome-flags-for-tools.md 4 | * https://peter.sh/experiments/chromium-command-line-switches/ 5 | */ 6 | export const chromeAndroidOptions = [ 7 | '--disable-fre', 8 | // : Disable reporting to UMA, but allows for collection 9 | '--metrics-recording-only', 10 | '--disable-background-networking', 11 | '--disable-component-update', 12 | '--no-default-browser-check', 13 | '--no-first-run', 14 | '--allow-running-insecure-content', 15 | '--disable-client-side-phishing-detection', 16 | '--disable-device-discovery-notifications', 17 | '--disable-default-apps', 18 | '--disable-domain-reliability', 19 | '--disable-background-timer-throttling', 20 | '--disable-external-intent-requests', 21 | '--disable-search-engine-choice-screen', 22 | '--enable-remote-debugging', 23 | '--mute-audio', 24 | '--disable-hang-monitor', 25 | '--password-store=basic', 26 | '--disable-breakpad', 27 | '--dont-require-litepage-redirect-infobar', 28 | '--override-https-image-compression-infobar', 29 | '--disable-fetching-hints-at-navigation-start' 30 | ]; 31 | -------------------------------------------------------------------------------- /lib/chrome/settings/chromeDesktopOptions.js: -------------------------------------------------------------------------------- 1 | // See https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md 2 | // https://peter.sh/experiments/chromium-command-line-switches/ 3 | export const chromeDesktopOptions = [ 4 | '--allow-running-insecure-content', 5 | '--disable-background-networking', 6 | '--disable-background-timer-throttling', 7 | '--disable-backgrounding-occluded-windows', 8 | '--disable-breakpad', 9 | '--disable-client-side-phishing-detection', 10 | '--disable-component-update', 11 | '--disable-default-apps', 12 | '--disable-dev-shm-usage', 13 | '--disable-domain-reliability', 14 | '--disable-fetching-hints-at-navigation-start', 15 | '--disable-hang-monitor', 16 | '--disable-ipc-flooding-protection', 17 | '--disable-prompt-on-repost', 18 | '--disable-renderer-backgrounding', 19 | '--disable-search-engine-choice-screen', 20 | '--disable-site-isolation-trials', 21 | '--disable-sync', 22 | '--metrics-recording-only', 23 | '--mute-audio', 24 | '--new-window', 25 | '--no-default-browser-check', 26 | '--no-first-run', 27 | '--password-store=basic', 28 | '--use-mock-keychain' 29 | ]; 30 | -------------------------------------------------------------------------------- /lib/chrome/settings/traceCategories.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Default trace categories for the Chrome trace log. 3 | */ 4 | export const traceCategories = [ 5 | '-*', 6 | 'disabled-by-default-lighthouse', 7 | 'v8', 8 | 'v8.execute', 9 | 'blink.user_timing', 10 | 'devtools.timeline', 11 | 'disabled-by-default-devtools.timeline', 12 | 'disabled-by-default-devtools.timeline.stack', 13 | 'disabled-by-default-devtools.timeline.frame', 14 | 'disabled-by-default-devtools.timeline.invalidationTracking', 15 | 'loading', 16 | 'latencyInfo' 17 | ]; 18 | -------------------------------------------------------------------------------- /lib/chrome/webdriver/devtools/domComplete.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/devtools/performance/extension 2 | 3 | export const domCompleteTimeLine = ` 4 | (function () { 5 | performance.mark('DOM Complete', { 6 | startTime: window.performance.getEntriesByType('navigation')[0].domComplete, 7 | detail: { 8 | devtools: { 9 | dataType: 'marker', 10 | trackGroup: 'Browsertime Timeline', 11 | track: 'Metrics', 12 | color: 'tertiary-dark', 13 | tooltipText: 'DOM Complete', 14 | properties: [ 15 | ['DOMComplete', window.performance.getEntriesByType('navigation')[0].domComplete] 16 | ] 17 | } 18 | } 19 | }); 20 | })(); 21 | `; 22 | -------------------------------------------------------------------------------- /lib/chrome/webdriver/devtools/element.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/devtools/performance/extension 2 | 3 | export const elementTimeLine = ` 4 | (function () { 5 | const observer = new PerformanceObserver(list => { }); 6 | observer.observe({ type: 'element', buffered: true }); 7 | const entries = observer.takeRecords(); 8 | for (let entry of entries) { 9 | performance.mark(entry.identifier, { 10 | startTime: entry.renderTime, 11 | detail: { 12 | devtools: { 13 | dataType: 'marker', 14 | track: 'Metrics', 15 | trackGroup: 'Browsertime Timeline', 16 | color: 'primary', 17 | tooltipText: entry.identifier + ' element', 18 | properties: [ 19 | ['tagName', '' + entry.element ? entry.element.tagName:''], 20 | ['className', '' + entry.element ? entry.element.className:''], 21 | ['URL', '' + entry.url] 22 | ['Render', entry.renderTime] 23 | ], 24 | } 25 | } 26 | }); 27 | } 28 | })(); 29 | `; 30 | -------------------------------------------------------------------------------- /lib/chrome/webdriver/devtools/fcp.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/devtools/performance/extension 2 | 3 | export const fcpTimeLine = ` 4 | (function () { 5 | const observer = new PerformanceObserver(list => { }); 6 | observer.observe({ type: 'paint', buffered: true }); 7 | const entries = observer.takeRecords(); 8 | for (let entry of entries) { 9 | performance.mark(entry.name === 'first-contentful-paint' ? 'FCP': 'FP', { 10 | startTime: entry.startTime, 11 | detail: { 12 | devtools: { 13 | dataType: 'marker', 14 | track: 'Metrics', 15 | trackGroup: 'Browsertime Timeline', 16 | color: 'primary', 17 | tooltipText: entry.name 18 | } 19 | } 20 | }); 21 | } 22 | })(); 23 | `; 24 | -------------------------------------------------------------------------------- /lib/chrome/webdriver/devtools/loadEventEnd.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/devtools/performance/extension 2 | 3 | export const loadEventEndTimeLine = ` 4 | (function () { 5 | performance.mark('Load Event End', { 6 | startTime: performance.getEntriesByType('navigation')[0].loadEventEnd, 7 | detail: { 8 | devtools: { 9 | dataType: 'marker', 10 | trackGroup: 'Browsertime Timeline', 11 | track: 'Metrics', 12 | color: 'secondary', 13 | tooltipText: 'Load Event End', 14 | properties: [ 15 | ['LoadEventEnd', window.performance.getEntriesByType('navigation')[0].loadEventEnd] 16 | ] 17 | } 18 | } 19 | }); 20 | })(); 21 | `; 22 | -------------------------------------------------------------------------------- /lib/chrome/webdriver/devtools/longtask.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/devtools/performance/extension 2 | 3 | export const longtaskTimeLine = ` 4 | (function () { 5 | const observer = new PerformanceObserver(list => { }); 6 | observer.observe({ type: 'longtask', buffered: true, durationThreshold: 0 }); 7 | const entries = observer.takeRecords(); 8 | for (let entry of entries) { 9 | performance.measure('Long Task', { 10 | start: entry.startTime, 11 | end: entry.startTime + entry.duration, 12 | detail: { 13 | devtools: { 14 | dataType: 'track-entry', 15 | track: 'Long Task', 16 | trackGroup: 'Browsertime Timeline', 17 | color: 'error', 18 | tooltipText: 'Long Task' 19 | } 20 | } 21 | }); 22 | } 23 | })(); 24 | `; 25 | -------------------------------------------------------------------------------- /lib/chrome/webdriver/devtools/ttfb.js: -------------------------------------------------------------------------------- 1 | // https://developer.chrome.com/docs/devtools/performance/extension 2 | 3 | export const ttfbTimeLine = ` 4 | (function () { 5 | performance.mark('TTFB', { 6 | startTime: window.performance.getEntriesByType('navigation')[0].responseStart, 7 | detail: { 8 | devtools: { 9 | dataType: 'marker', 10 | trackGroup: 'Browsertime Timeline', 11 | track: 'Metrics', 12 | color: 'tertiary-light', 13 | tooltipText: 'Time To First Byte (TTFB)', 14 | properties: [ 15 | ['TTFB', window.performance.getEntriesByType('navigation')[0].responseStart] 16 | ] 17 | } 18 | } 19 | }); 20 | })(); 21 | `; 22 | -------------------------------------------------------------------------------- /lib/core/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-restricted-modules": [2, "fs"] 4 | } 5 | } -------------------------------------------------------------------------------- /lib/core/engine/command/meta.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Add meta data to your user journey. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class Meta { 8 | constructor() {} 9 | 10 | /** 11 | * Sets the description for the user journey. 12 | * @example commands.meta.setDescription('My test'); 13 | * @param {string} text - The text to set as the description. 14 | */ 15 | setDescription(text) { 16 | this.description = text; 17 | } 18 | 19 | /** 20 | * Sets the title for the user journey. 21 | * @example commands.meta.setTitle('Test title'); 22 | * @param {string} text - The text to set as the title. 23 | */ 24 | setTitle(text) { 25 | this.title = text; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/core/engine/command/mouse/index.js: -------------------------------------------------------------------------------- 1 | export { ClickAndHold } from './clickAndHold.js'; 2 | export { DoubleClick } from './doubleClick.js'; 3 | export { ContextClick } from './contextClick.js'; 4 | export { SingleClick } from './singleClick.js'; 5 | export { MouseMove } from './mouseMove.js'; 6 | -------------------------------------------------------------------------------- /lib/core/engine/command/screenshot.js: -------------------------------------------------------------------------------- 1 | import { getLogger } from '@sitespeed.io/log'; 2 | const log = getLogger('browsertime.command.screenshot'); 3 | 4 | /** 5 | * Take a screenshot. The screenshot will be stored to disk, 6 | * named by the name provided to the take function. 7 | * 8 | * @class 9 | * @hideconstructor 10 | */ 11 | export class Screenshot { 12 | constructor(screenshotManager, browser, index) { 13 | /** 14 | * @private 15 | */ 16 | this.screenshotManager = screenshotManager; 17 | /** 18 | * @private 19 | */ 20 | this.browser = browser; 21 | /** 22 | * @private 23 | */ 24 | this.index = index; 25 | } 26 | 27 | /** 28 | * Takes a screenshot and saves it using the screenshot manager. 29 | * 30 | * @async 31 | * @example async commands.screenshot.take('my_startpage'); 32 | * @param {string} name The name to assign to the screenshot file. 33 | * @throws {Error} Throws an error if the name parameter is not provided. 34 | * @returns {Promise} A promise that resolves with the screenshot details. 35 | */ 36 | 37 | async take(name) { 38 | if (!name) { 39 | log.error('Missing name for the screenshot'); 40 | throw new Error(`Could not store screenshot for missing name`); 41 | } 42 | const url = await this.browser 43 | .getDriver() 44 | .executeScript('return document.documentURI;'); 45 | const screenshot = await this.browser.takeScreenshot(); 46 | return this.screenshotManager.save(name, screenshot, url, this.index); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/core/engine/run.js: -------------------------------------------------------------------------------- 1 | export function run(urlOrFunctions) { 2 | return async function (context, commands) { 3 | for (let urlOrFunction of urlOrFunctions) { 4 | await (typeof urlOrFunction === 'function' 5 | ? urlOrFunction(context, commands) 6 | : commands.measure.start(urlOrFunction)); 7 | } 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /lib/core/pageCompleteChecks/defaultPageCompleteCheck.js: -------------------------------------------------------------------------------- 1 | export const defaultPageCompleteCheck = ` 2 | return (function(waitTime) { 3 | try { 4 | var end = window.performance.timing.loadEventEnd; 5 | var start= window.performance.timing.navigationStart; 6 | return (end > 0) && (performance.now() > end - start + waitTime); 7 | } 8 | catch(e) { 9 | return true; 10 | } 11 | })(arguments[arguments.length - 1]); 12 | `; 13 | -------------------------------------------------------------------------------- /lib/core/pageCompleteChecks/pageCompleteCheckByInactivity.js: -------------------------------------------------------------------------------- 1 | export const pageCompleteCheckByInactivity = ` 2 | return (function(waitTime) { 3 | const timing = window.performance.timing; 4 | const p = window.performance; 5 | const limit = waitTime; 6 | 7 | if (timing.loadEventEnd === 0) { 8 | return false; 9 | } 10 | 11 | let lastEntry = null; 12 | const resourceTimings = p.getEntriesByType('resource'); 13 | if (resourceTimings.length > 0) { 14 | lastEntry = resourceTimings.pop(); 15 | // This breaks getting resource timings so ... 16 | p.clearResourceTimings(); 17 | } 18 | 19 | const loadTime = timing.loadEventEnd - timing.navigationStart; 20 | 21 | if (!lastEntry || lastEntry.responseEnd < loadTime) { 22 | return p.now() - loadTime > limit; 23 | } else { 24 | return p.now() - lastEntry.responseEnd > limit; 25 | } 26 | })(arguments[arguments.length - 1]); 27 | `; 28 | -------------------------------------------------------------------------------- /lib/core/pageCompleteChecks/spaInactivity.js: -------------------------------------------------------------------------------- 1 | export const spaInactivity = ` 2 | return (function(waitTime) { 3 | const timing = window.performance.timing; 4 | const p = window.performance; 5 | const resourceTimings = p.getEntriesByType('resource'); 6 | if (resourceTimings.length > 0) { 7 | const lastEntry = resourceTimings.pop(); 8 | const stop = p.now() - lastEntry.responseEnd > waitTime; 9 | if (stop) { 10 | // empty resource timings for the next run 11 | p.clearResourceTimings(); 12 | return true; 13 | } 14 | } 15 | else return false; 16 | })(arguments[arguments.length - 1]); 17 | `; 18 | -------------------------------------------------------------------------------- /lib/firefox/settings/disableSafeBrowsingPreferences.js: -------------------------------------------------------------------------------- 1 | export const disableSafeBrowsingPreferences = { 2 | 'browser.safebrowsing.downloads.enabled': false, 3 | 'browser.safebrowsing.enabled': false, 4 | 'browser.safebrowsing.phishing.enabled': false, 5 | 'browser.safebrowsing.malware.enabled': false, 6 | 'browser.safebrowsing.remotelookups': false, 7 | 'browser.safebrowsing.provider.mozilla.updateURL': '', 8 | 'browser.safebrowsing.provider.google4.dataSharingURL': '', 9 | 'browser.safebrowsing.provider.google4.gethashURL': '', 10 | 'browser.safebrowsing.provider.google4.lists': '', 11 | 'browser.safebrowsing.provider.google4.advisoryURL': '', 12 | 'browser.safebrowsing.provider.google.updateURL': '', 13 | 'browser.safebrowsing.provider.google.reportURL': '', 14 | 'browser.safebrowsing.provider.google.advisoryURL': '', 15 | 'browser.safebrowsing.provider.google.gethashURL': '', 16 | 'browser.safebrowsing.provider.google.lists': '', 17 | 'browser.safebrowsing.provider.google4.reportURL': '', 18 | 'browser.safebrowsing.provider.google4.updateURL': '', 19 | 'browser.safebrowsing.provider.mozilla.gethashURL': '', 20 | 'browser.safebrowsing.provider.google.reportMalwareMistakeURL': '', 21 | 'browser.safebrowsing.provider.google.reportPhishMistakeURL': '' 22 | }; 23 | -------------------------------------------------------------------------------- /lib/firefox/settings/disableTrackingProtectionPreferences.js: -------------------------------------------------------------------------------- 1 | export const disableTrackingProtectionPreferences = { 2 | 'privacy.trackingprotection.annotate_channels': false, 3 | 'privacy.trackingprotection.enabled': false, 4 | 'privacy.trackingprotection.introURL': 5 | 'http://127.0.0.1/trackingprotection/tour', 6 | 'privacy.trackingprotection.pbmode.enabled': false 7 | }; 8 | -------------------------------------------------------------------------------- /lib/firefox/settings/geckoProfilerDefaults.js: -------------------------------------------------------------------------------- 1 | export const geckoProfilerDefaults = { 2 | features: 'js,stackwalk,leaf', 3 | threads: 'GeckoMain,Compositor,Renderer', 4 | desktopSamplingInterval: 1, 5 | androidSamplingInterval: 4, 6 | bufferSize: 13_107_200 // 100MB 7 | }; 8 | -------------------------------------------------------------------------------- /lib/safari/webdriver/builder.js: -------------------------------------------------------------------------------- 1 | import { Options } from 'selenium-webdriver/safari.js'; 2 | 3 | export async function configureBuilder(builder, baseDir, options) { 4 | const safariOptions = options.safari || {}; 5 | builder 6 | .getCapabilities() 7 | .set( 8 | 'platformName', 9 | safariOptions.ios || safariOptions.useSimulator ? 'iOS' : 'mac' 10 | ); 11 | 12 | if (safariOptions.deviceName) { 13 | builder 14 | .getCapabilities() 15 | .set('safari:deviceName', safariOptions.deviceName); 16 | } 17 | 18 | if (safariOptions.deviceUDID) { 19 | builder 20 | .getCapabilities() 21 | .set('safari:deviceUDID', safariOptions.deviceUDID); 22 | } 23 | 24 | if (safariOptions.deviceType) { 25 | builder 26 | .getCapabilities() 27 | .set('safari:deviceType', safariOptions.deviceType); 28 | } 29 | 30 | if (safariOptions.useSimulator) { 31 | builder 32 | .getCapabilities() 33 | .set('safari:useSimulator', safariOptions.useSimulator); 34 | } 35 | 36 | if (safariOptions.diagnose) { 37 | builder.getCapabilities().set('safari:diagnose', true); 38 | } 39 | 40 | if (safariOptions.ios) { 41 | builder.usingServer(`http://localhost:${options.safariDriverPort}`); 42 | } 43 | 44 | if (safariOptions.useTechnologyPreview) { 45 | let o = new Options(); 46 | o = o.setTechnologyPreview(true); 47 | o.setBrowserName('Safari Technology Preview'); 48 | builder.setSafariOptions(o); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/screenshot/defaults.js: -------------------------------------------------------------------------------- 1 | export const screenshotDefaults = { 2 | type: 'jpg', 3 | png: { 4 | compressionLevel: 6 5 | }, 6 | jpg: { 7 | quality: 80 8 | }, 9 | maxSize: 2000 10 | }; 11 | -------------------------------------------------------------------------------- /lib/screenshot/loadCustomJimp.js: -------------------------------------------------------------------------------- 1 | export async function loadCustomJimp() { 2 | try { 3 | const { default: configure } = await import('@jimp/custom'); 4 | const { default: pluginPng } = await import('@jimp/png'); 5 | const { default: pluginJpeg } = await import('@jimp/jpeg'); 6 | const { default: pluginScale } = await import('@jimp/plugin-scale'); 7 | // The scale plugin use resize 8 | const { default: pluginResize } = await import('@jimp/plugin-resize'); 9 | const jimp = configure({ 10 | types: [pluginPng, pluginJpeg], 11 | plugins: [pluginResize, pluginScale] 12 | }); 13 | return jimp; 14 | } catch { 15 | return; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/support/dns.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | import { isAndroidConfigured } from '../android/index.js'; 4 | const log = getLogger('browsertime.dns'); 5 | 6 | export async function flushDNS(options) { 7 | if (isAndroidConfigured(options)) { 8 | return; 9 | } 10 | 11 | if (options.safari && options.safari.ios) { 12 | return; 13 | } 14 | 15 | if (process.platform === 'darwin') { 16 | log.info('Flush DNS cache on MacOS'); 17 | await execa('sudo', ['killall', '-HUP', 'mDNSResponder'], { 18 | reject: false 19 | }); 20 | await execa('sudo', ['dscacheutil', '-flushcache'], { 21 | reject: false 22 | }); 23 | return execa('sudo', ['lookupd', '-flushcache'], { 24 | reject: false 25 | }); 26 | } else if (process.platform === 'linux') { 27 | log.info('Flush DNS cache on Linux'); 28 | 29 | // Unbuntu 22.04 30 | await execa('sudo', ['resolvectl', 'flush-caches'], { 31 | reject: false 32 | }); 33 | await execa('sudo', ['systemd-resolve', '--flush-caches'], { 34 | reject: false 35 | }); 36 | await execa('sudo', ['service', 'dnsmasq', 'restart'], { 37 | reject: false 38 | }); 39 | await execa('sudo', ['rndc', 'restart'], { 40 | reject: false 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/support/errors.js: -------------------------------------------------------------------------------- 1 | export class BrowsertimeError extends Error { 2 | constructor(message, extra) { 3 | super(message); 4 | this.extra = extra || {}; 5 | this.name = this.constructor.name; 6 | } 7 | } 8 | 9 | export class BrowserError extends BrowsertimeError { 10 | constructor(message, extra) { 11 | super(message, extra); 12 | } 13 | } 14 | 15 | export class UrlLoadError extends BrowsertimeError { 16 | constructor(message, url, extra) { 17 | super(message, extra); 18 | this.url = url; 19 | } 20 | } 21 | 22 | export class TimeoutError extends BrowsertimeError { 23 | constructor(message, url, extra) { 24 | super(message, extra); 25 | this.url = url; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/support/filters.js: -------------------------------------------------------------------------------- 1 | import { promisify } from 'node:util'; 2 | import { stat as _stat } from 'node:fs'; 3 | import path from 'node:path'; 4 | const stat = promisify(_stat); 5 | 6 | /** 7 | * Filters to use with Array.prototype.filter, e.g. ['/a/path', '/another/path'].filter(onlyFiles) 8 | */ 9 | export function onlyWithExtension(extension) { 10 | return filepath => path.extname(filepath) === extension; 11 | } 12 | export async function onlyFiles(filepath) { 13 | const stats = await stat(filepath); 14 | return stats.isFile(); 15 | } 16 | export async function onlyDirectories(filepath) { 17 | const stats = await stat(filepath); 18 | return stats.isDirectory(); 19 | } 20 | -------------------------------------------------------------------------------- /lib/support/getPort.js: -------------------------------------------------------------------------------- 1 | import { createServer } from 'node:net'; 2 | 3 | export async function getAvailablePort(portRange, host = '127.0.0.1') { 4 | const startPort = portRange[0]; 5 | const endPort = portRange[1]; 6 | 7 | for (let port = startPort; port <= endPort; port++) { 8 | if (await isPortAvailable(port, host)) { 9 | return port; 10 | } 11 | } 12 | 13 | throw new Error(`No available ports found in range ${startPort}-${endPort}`); 14 | } 15 | 16 | function isPortAvailable(port, host) { 17 | return new Promise(resolve => { 18 | const server = createServer(); 19 | server.unref(); 20 | server.on('error', () => resolve(false)); 21 | server.listen(port, host, () => { 22 | server.close(() => resolve(true)); 23 | }); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /lib/support/logging.js: -------------------------------------------------------------------------------- 1 | // configure.js 2 | import { configureLog } from '@sitespeed.io/log'; 3 | 4 | /** 5 | * Adapted from old intel-based configure logic. 6 | * 7 | * @param {Object} options 8 | * @param {number} [options.verbose=0] - 0: Info, 1: Debug, 2: Verbose, 3: Trace 9 | * @param {boolean} [options.silent=false] - If true, disables logging 10 | */ 11 | export function configure(options = {}) { 12 | configureLog({ 13 | level: options.logLevel ?? undefined, 14 | verbose: options.verbose ?? 0, 15 | silent: options.silent ?? false 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /lib/support/preURL.js: -------------------------------------------------------------------------------- 1 | import { getLogger } from '@sitespeed.io/log'; 2 | const log = getLogger('browsertime'); 3 | const delay = ms => new Promise(res => setTimeout(res, ms)); 4 | 5 | export async function preURL( 6 | browser, 7 | engineDelegate, 8 | pageCompleteCheck, 9 | options 10 | ) { 11 | log.info('Accessing preURL %s', options.preURL); 12 | await browser.loadAndWait(options.preURL, pageCompleteCheck, engineDelegate); 13 | if (!options.preURLDisableWhiteBackground) { 14 | await browser.runScript( 15 | 'document.body.innerHTML = ""; document.body.style.backgroundColor = "white";', 16 | 'WHITE_BACKGROUND' 17 | ); 18 | } 19 | 20 | return delay(options.preURLDelay ?? 1500); 21 | } 22 | -------------------------------------------------------------------------------- /lib/support/processes.js: -------------------------------------------------------------------------------- 1 | import { execaCommand as command } from 'execa'; 2 | export async function getNumberOfRunningProcesses() { 3 | const { stdout } = await command('ps aux | wc -l', { shell: true }); 4 | return stdout; 5 | } 6 | -------------------------------------------------------------------------------- /lib/support/setResourceTimingBufferSize.js: -------------------------------------------------------------------------------- 1 | export async function setResourceTimingBufferSize(startURL, driver, size) { 2 | await driver.get(startURL); 3 | await driver.executeScript( 4 | `window.performance.setResourceTimingBufferSize(${size});` 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /lib/support/stop.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime'); 4 | 5 | export async function stop(processName) { 6 | const scriptArguments = ['-9', processName]; 7 | 8 | log.debug('Kill all processes ' + processName); 9 | 10 | return execa('pkill', scriptArguments, { reject: false }); 11 | } 12 | -------------------------------------------------------------------------------- /lib/support/tcpdump.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { execa } from 'execa'; 3 | import { pathToFolder } from './pathToFolder.js'; 4 | import { rename } from './fileUtil.js'; 5 | 6 | export class TCPDump { 7 | constructor(directory, options) { 8 | this.baseDir = directory; 9 | this.options = options; 10 | } 11 | async start(iteration) { 12 | const captureFile = path.join(this.baseDir, iteration + '.pcap'); 13 | const parameters = [ 14 | 'tcpdump', 15 | '-i', 16 | 'any', 17 | '-s', 18 | '0', 19 | '-p', 20 | '-w', 21 | captureFile 22 | ]; 23 | if (this.options.tcpdumpPacketBuffered) { 24 | parameters.push('-U'); 25 | } 26 | if (this.options.tcpDumpParams) { 27 | const extras = Array.isArray(this.options.tcpDumpParams) 28 | ? this.options.tcpDumpParams 29 | : [this.options.tcpDumpParams]; 30 | parameters.push(...extras); 31 | } 32 | this.tcpdumpProcess = execa('sudo', parameters); 33 | } 34 | async stop() { 35 | if (this.tcpdumpProcess) { 36 | await this.tcpdumpProcess.kill('SIGINT'); 37 | this.tcpdumpProcess = undefined; 38 | } 39 | } 40 | 41 | async mv(url, iteration) { 42 | const oldLocation = path.join(this.baseDir, iteration + '.pcap'); 43 | const newLocation = path.join( 44 | this.baseDir, 45 | pathToFolder(url, this.options), 46 | iteration + '.pcap' 47 | ); 48 | return rename(oldLocation, newLocation); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/support/usbPower.js: -------------------------------------------------------------------------------- 1 | export async function loadUsbPowerProfiler() { 2 | try { 3 | // usb-power-profiling/usb-power-profiling.js exports a default, so we destructure it 4 | const { default: usbPowerProfiler } = await import( 5 | 'usb-power-profiling/usb-power-profiling.js' 6 | ); 7 | return usbPowerProfiler; 8 | } catch { 9 | return; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/support/userTiming.js: -------------------------------------------------------------------------------- 1 | export function filterAllowlisted(userTimings, allowlistRegex) { 2 | const allowed = new RegExp(allowlistRegex); 3 | userTimings.marks = userTimings.marks.filter(mark => allowed.test(mark.name)); 4 | userTimings.measures = userTimings.measures.filter(measure => 5 | allowed.test(measure.name) 6 | ); 7 | } 8 | 9 | export function filterBlocklisted(userTimings, blocklistRegex) { 10 | const blocked = new RegExp(blocklistRegex); 11 | userTimings.marks = userTimings.marks.filter( 12 | mark => !blocked.test(mark.name) 13 | ); 14 | userTimings.measures = userTimings.measures.filter( 15 | measure => !blocked.test(measure.name) 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /lib/video/defaults.js: -------------------------------------------------------------------------------- 1 | export const framerate = 30; 2 | export const crf = 23; 3 | export const xvfbDisplay = 99; 4 | export const addTimer = true; 5 | export const convert = true; 6 | export const threads = 0; 7 | -------------------------------------------------------------------------------- /lib/video/postprocessing/finetune/addTextToVideo.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | import { getTimingMetrics } from './getTimingMetrics.js'; 4 | import { getFont } from './getFont.js'; 5 | import { isAndroidConfigured } from '../../../android/index.js'; 6 | const log = getLogger('browsertime.video'); 7 | 8 | function isSmallish(options) { 9 | return ( 10 | (options.chrome && 11 | options.chrome.mobileEmulation && 12 | options.chrome.mobileEmulation.deviceName) || 13 | isAndroidConfigured(options) 14 | ); 15 | } 16 | 17 | export async function addTextToVideo( 18 | inputFile, 19 | outputFile, 20 | videoMetrics, 21 | timingMetrics, 22 | options 23 | ) { 24 | /** Add timer and metrics to the video */ 25 | const arguments_ = ['-nostdin', '-i', inputFile, '-c:v', 'libx264']; 26 | const allTimingMetrics = getTimingMetrics( 27 | videoMetrics, 28 | timingMetrics, 29 | options 30 | ); 31 | const fontFile = getFont(options); 32 | 33 | let fontSize = 16; 34 | if (isSmallish(options)) { 35 | fontSize = 22; 36 | } 37 | 38 | if (options.safari && options.safari.useSimulator) { 39 | fontSize = 32; 40 | } 41 | 42 | arguments_.push( 43 | '-vf', 44 | `drawtext=${fontFile}x=w/2-(tw/2): y=H-h/10:fontcolor=white:fontsize=h/${fontSize}:box=1:boxcolor=0x000000AA:text='%{pts\\:hms}'${allTimingMetrics}`, 45 | '-y', 46 | outputFile 47 | ); 48 | log.verbose('Adding text with FFMPEG ' + arguments_.join(' ')); 49 | return execa('ffmpeg', arguments_); 50 | } 51 | -------------------------------------------------------------------------------- /lib/video/postprocessing/finetune/convertFps.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime.video'); 4 | 5 | export async function convert(source, destination, framerate) { 6 | const scriptArguments = [ 7 | '-nostdin', 8 | '-i', 9 | source, 10 | '-r', 11 | framerate, 12 | destination 13 | ]; 14 | log.info('Converting video to %s fps', framerate); 15 | return execa('ffmpeg', scriptArguments); 16 | } 17 | -------------------------------------------------------------------------------- /lib/video/postprocessing/finetune/getFont.js: -------------------------------------------------------------------------------- 1 | import { existsSync } from 'node:fs'; 2 | export function getFont(options) { 3 | // If the font is not part of the params and we're on macOS 4 | // we check that SFNSMono.ttf is available 5 | if (!options.videoParams.fontPath && process.platform === 'darwin') { 6 | const systemFontFile = '/System/Library/Fonts/SFNSMono.ttf'; 7 | try { 8 | if (existsSync(systemFontFile)) { 9 | return systemFontFile + ':'; 10 | } 11 | return ''; 12 | } catch { 13 | return ''; 14 | } 15 | } else if (!options.videoParams.fontPath && options.docker) { 16 | // Mono font Ubuntu 20.04 17 | const systemFontFile = 18 | '/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf'; 19 | try { 20 | if (existsSync(systemFontFile)) { 21 | return systemFontFile + ':'; 22 | } 23 | return ''; 24 | } catch { 25 | return ''; 26 | } 27 | } else 28 | return options.videoParams.fontPath 29 | ? options.videoParams.fontPath + ':' 30 | : ''; 31 | } 32 | -------------------------------------------------------------------------------- /lib/video/postprocessing/finetune/removeOrange.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime.video'); 4 | 5 | export async function removeOrange( 6 | inputFile, 7 | outputFile, 8 | newStart, 9 | visualMetrics, 10 | options 11 | ) { 12 | // Remove the orange frames from the video (and stop the video after last visual change) 13 | const arguments_ = ['-nostdin', '-ss', newStart, '-i', inputFile]; 14 | if (options.visualMetrics) { 15 | // End the video on last visual change + 1 s 16 | const end = newStart + visualMetrics.LastVisualChange / 1000 + 1; 17 | arguments_.push('-to', end); 18 | } 19 | arguments_.push('-c', 'copy', outputFile); 20 | 21 | log.verbose('Removing orange start with FFMPEG ' + arguments_.join(' ')); 22 | return execa('ffmpeg', arguments_); 23 | } 24 | -------------------------------------------------------------------------------- /lib/video/postprocessing/visualmetrics/getVideoMetrics.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | import { run } from './visualMetrics.js'; 4 | import { extraMetrics } from './extraMetrics.js'; 5 | const log = getLogger('browsertime.video'); 6 | 7 | export async function getVideoMetrics( 8 | videoDir, 9 | filmstripDir, 10 | videoPath, 11 | index, 12 | visualElements, // results.browserScripts.pageinfo.visualElements 13 | storageManager, 14 | pageNumber, 15 | visitedPageNumber, 16 | options 17 | ) { 18 | log.debug('Running visualMetrics'); 19 | // If we want to use the Hero functionality of Visual Metrics 20 | // we need to create the hero JSON file. 21 | if (options.visualElements && visualElements) { 22 | await storageManager.writeJson( 23 | index + '-visualElements.json', 24 | visualElements, 25 | true 26 | ); 27 | } 28 | const elementsFile = path.join( 29 | storageManager.directory, 30 | index + '-visualElements.json.gz' 31 | ); 32 | try { 33 | const metrics = await run( 34 | videoPath, 35 | filmstripDir, 36 | elementsFile, 37 | videoDir, 38 | index, 39 | pageNumber, 40 | visitedPageNumber, 41 | options 42 | ); 43 | log.debug('Collected metrics ' + JSON.stringify(metrics)); 44 | return extraMetrics(metrics); 45 | } catch (error) { 46 | log.error('Could not run Visual Metrics', error); 47 | throw error; 48 | } finally { 49 | // Remove the file 50 | if (options.visualElements && visualElements) { 51 | await storageManager.rm(index + '-visualElements.json.gz'); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/video/screenRecording/android/recorder.js: -------------------------------------------------------------------------------- 1 | import { promisify } from 'node:util'; 2 | import { unlink as _unlink } from 'node:fs'; 3 | import { getLogger } from '@sitespeed.io/log'; 4 | import { framerate } from '../../defaults.js'; 5 | import { Android } from '../../../android/index.js'; 6 | import { getProperty } from '../../../support/util.js'; 7 | 8 | const unlink = promisify(_unlink); 9 | const log = getLogger('browsertime.video'); 10 | const delay = ms => new Promise(res => setTimeout(res, ms)); 11 | 12 | export class AndroidRecorder { 13 | constructor(options) { 14 | this.waitTime = getProperty( 15 | options, 16 | 'videoParams.androidVideoWaitTime', 17 | 5000 18 | ); 19 | this.framerate = getProperty(options, 'videoParams.framerate', framerate); 20 | this.options = options; 21 | } 22 | 23 | async start() { 24 | this.android = new Android(this.options); 25 | return this.android.startVideo(); 26 | } 27 | 28 | async stop(destination) { 29 | log.debug('Stop screen recording'); 30 | await this.android.stopVideo(); 31 | // We want to wait some extra time for the video to land on the device 32 | await delay(this.waitTime); 33 | // The destination file could exist of we use --resultDir 34 | // so make sure we remove it first 35 | if (this.options.resultDir) { 36 | try { 37 | await unlink(destination); 38 | } catch { 39 | // Nothing to see here 40 | } 41 | } 42 | await this.android.pullVideo(destination); 43 | return this.android.removeVideo(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/video/screenRecording/desktop/convert.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime.video'); 4 | 5 | export async function convert(source, destination, crf, threads) { 6 | const scriptArguments = [ 7 | '-nostdin', 8 | '-i', 9 | source, 10 | '-c:v', 11 | 'libx264', 12 | '-threads', 13 | threads, 14 | '-crf', 15 | crf, 16 | '-preset', 17 | 'fast', 18 | '-vf', 19 | 'format=yuv420p', 20 | destination 21 | ]; 22 | 23 | log.debug( 24 | 'Converting video to viewable format with args %j', 25 | scriptArguments 26 | ); 27 | 28 | return execa('ffmpeg', scriptArguments); 29 | } 30 | -------------------------------------------------------------------------------- /lib/video/screenRecording/desktop/osx/getSPDisplaysDataType.js: -------------------------------------------------------------------------------- 1 | import { execaCommand as command } from 'execa'; 2 | 3 | export async function getSPDisplaysDataType() { 4 | const output = await command('system_profiler SPDisplaysDataType', { 5 | shell: true 6 | }); 7 | return output.stdout; 8 | } 9 | -------------------------------------------------------------------------------- /lib/video/screenRecording/desktop/osx/getScreen.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime.video'); 4 | 5 | export async function getScreenOnOSX() { 6 | const scriptArguments = [ 7 | '-hide_banner', 8 | '-f', 9 | 'avfoundation', 10 | '-list_devices', 11 | true, 12 | '-i', 13 | 0 14 | ]; 15 | log.debug('Getting screen on OS X using %j', scriptArguments); 16 | 17 | /* 18 | ffmpeg -hide_banner -f avfoundation -list_devices true -i 0 19 | Output like ... 20 | [AVFoundation input device @ 0x7fb919e00780] AVFoundation video devices: 21 | [AVFoundation input device @ 0x7fb919e00780] [0] FaceTime HD Camera 22 | [AVFoundation input device @ 0x7fb919e00780] [1] Capture screen 0 23 | [AVFoundation input device @ 0x7fb919e00780] AVFoundation audio devices: 24 | [AVFoundation input device @ 0x7fb919e00780] [0] Built-in Microphone 25 | */ 26 | 27 | const output = await execa('ffmpeg', scriptArguments, { reject: false }); 28 | log.debug('Output: %s', output.stderr); 29 | const reg = /\[(\d+)] Capture screen/; 30 | return output.stderr.match(reg)[1]; 31 | } 32 | -------------------------------------------------------------------------------- /lib/video/screenRecording/ios/convertToMp4.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime.video'); 4 | 5 | export async function convert(source, destination) { 6 | const scriptArguments = [ 7 | '-framerate', 8 | 60, 9 | '-i', 10 | source, 11 | '-c:v', 12 | 'copy', 13 | '-f', 14 | 'mp4', 15 | destination, 16 | '-y' 17 | ]; 18 | 19 | log.debug('Converting video from h264 to mp4 %j', scriptArguments); 20 | 21 | return execa('ffmpeg', scriptArguments); 22 | } 23 | -------------------------------------------------------------------------------- /lib/video/screenRecording/iosSimulator/convertToMp4.js: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { getLogger } from '@sitespeed.io/log'; 3 | const log = getLogger('browsertime.video'); 4 | 5 | export async function convert(source, destination) { 6 | const scriptArguments = [ 7 | // '-framerate', 8 | // 60, 9 | '-i', 10 | source, 11 | '-c:v', 12 | 'copy', 13 | '-f', 14 | 'mp4', 15 | destination, 16 | '-y' 17 | ]; 18 | 19 | log.debug('Converting video from h264 to mp4 %j', scriptArguments); 20 | 21 | return execa('ffmpeg', scriptArguments); 22 | } 23 | -------------------------------------------------------------------------------- /lib/video/screenRecording/iosSimulator/recorder.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | import { promisify } from 'node:util'; 3 | import fs from 'node:fs'; 4 | import { execaCommand } from 'execa'; 5 | import { getLogger } from '@sitespeed.io/log'; 6 | import { convert } from './convertToMp4.js'; 7 | const unlink = promisify(fs.unlink); 8 | const delay = ms => new Promise(res => setTimeout(res, ms)); 9 | const log = getLogger('browsertime.video'); 10 | 11 | export class IOSSimulatorRecorder { 12 | constructor(options, baseDir) { 13 | this.options = options; 14 | this.tmpVideo = path.join(baseDir, 'tmp.mov'); 15 | } 16 | 17 | async start() { 18 | log.info('Start IOS Simulator recorder.'); 19 | 20 | this.xcrunProcess = execaCommand( 21 | 'xcrun simctl io ' + 22 | this.options.safari.deviceUDID + 23 | ' recordVideo --code=h264 --mask=black --force ' + 24 | this.tmpVideo, 25 | { shell: true } 26 | ); 27 | } 28 | 29 | async stop(destination) { 30 | log.info('Stop IOS Simulator recorder.'); 31 | 32 | await this.xcrunProcess.kill('SIGINT', { 33 | forceKillAfterTimeout: 2000 34 | }); 35 | 36 | await delay(2500); 37 | await convert(this.tmpVideo, destination); 38 | await unlink(this.tmpVideo); 39 | return this.xcrunProcess; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/video/screenRecording/recorder.js: -------------------------------------------------------------------------------- 1 | import { isAndroidConfigured } from '../../android/index.js'; 2 | import { AndroidRecorder } from './android/recorder.js'; 3 | import { DesktopRecorder } from './desktop/desktopRecorder.js'; 4 | import { FirefoxWindowRecorder } from './firefox/firefoxWindowRecorder.js'; 5 | import { IOSSimulatorRecorder } from './iosSimulator/recorder.js'; 6 | import { IOSRecorder } from './ios/iosRecorder.js'; 7 | 8 | export function getRecorder(options, browser, baseDir) { 9 | if ( 10 | options.browser === 'firefox' && 11 | options.firefox && 12 | options.firefox.windowRecorder 13 | ) { 14 | return new FirefoxWindowRecorder(options, browser, baseDir); 15 | } 16 | 17 | if (isAndroidConfigured(options)) { 18 | return new AndroidRecorder(options); 19 | } 20 | 21 | if ( 22 | options.browser === 'safari' && 23 | options.safari && 24 | options.safari.useSimulator 25 | ) { 26 | return new IOSSimulatorRecorder(options, baseDir); 27 | } 28 | 29 | if (options.browser === 'safari' && options.safari && options.safari.ios) { 30 | return new IOSRecorder(options, baseDir); 31 | } 32 | 33 | return new DesktopRecorder(options); 34 | } 35 | -------------------------------------------------------------------------------- /lib/video/screenRecording/setOrangeBackground.js: -------------------------------------------------------------------------------- 1 | import { getLogger } from '@sitespeed.io/log'; 2 | import { until, By } from 'selenium-webdriver'; 3 | const log = getLogger('browsertime.video'); 4 | export async function setOrangeBackground(driver) { 5 | log.debug('Add orange color'); 6 | // We tried other ways for Android (access an orange page) 7 | // That works fine ... but break scripts 8 | // https://github.com/sitespeedio/browsertime/issues/802 9 | const orangeScript = ` 10 | (function() { 11 | const orange = document.createElement('div'); 12 | orange.id = 'browsertime-orange'; 13 | orange.style.position = 'absolute'; 14 | orange.style.top = '0'; 15 | orange.style.left = '0'; 16 | orange.style.width = Math.max(document.documentElement.clientWidth, document.body.clientWidth) + 'px'; 17 | orange.style.height = Math.max(document.documentElement.clientHeight,document.body.clientHeight) + 'px'; 18 | orange.style.backgroundColor = '#DE640D'; 19 | orange.style.zIndex = '2147483647'; 20 | orange.style.pointerEvents = 'none'; 21 | document.body.appendChild(orange); 22 | document.body.style.display = ''; 23 | })();`; 24 | 25 | await driver.executeScript(orangeScript); 26 | // It seems that in some cases the video do not have any orange at the start, so make sure we 27 | // the div orange div really exits 28 | return driver.wait(until.elementsLocated(By.id('browsertime-orange'))); 29 | } 30 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | # Super simple release script for browsertime 4 | # Lets use it it for now and make it better over time :) 5 | # You need np for this to work 6 | # npm install --global np 7 | 8 | # Remove the node modules and the result dir to start clean 9 | rm -fR browsertime-results 10 | 11 | # Genereate types 12 | npm run tsc 13 | 14 | np $* --no-yarn 15 | 16 | bin/browsertime.js --help > ../sitespeed.io/docs/documentation/browsertime/configuration/config.md 17 | 18 | bin/browsertime.js --version | tr -d '\n' > ../sitespeed.io/docs/_includes/version/browsertime.txt -------------------------------------------------------------------------------- /test/commandtests/actionTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine(); 28 | return engine.start(); 29 | }); 30 | 31 | serial('Run through the action API', async t => { 32 | const result = await engine.runMultiple([getPath('actions.cjs')], { 33 | scripts: { uri: 'document.documentURI' } 34 | }); 35 | t.deepEqual( 36 | result[0].browserScripts[0].scripts.uri, 37 | 'http://127.0.0.1:3000/simple/' 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /test/commandtests/chromeTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine({ 28 | browser: 'chrome', 29 | chrome: { 30 | timelineRecordingType: 'custom' 31 | } 32 | }); 33 | return engine.start(); 34 | }); 35 | 36 | serial('Run Chrome specific commands', async t => { 37 | const result = await engine.runMultiple([getPath('chrome.cjs')], { 38 | scripts: { 39 | uri: 'document.documentURI' 40 | } 41 | }); 42 | 43 | t.deepEqual( 44 | result[0].browserScripts[0].scripts.uri, 45 | 'http://127.0.0.1:3000/simple/' 46 | ); 47 | }); 48 | -------------------------------------------------------------------------------- /test/commandtests/firefoxTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine({ 28 | browser: 'firefox', 29 | firefox: { 30 | geckoProfilerRecordingType: 'custom' 31 | } 32 | }); 33 | return engine.start(); 34 | }); 35 | 36 | serial('Run Firefox specific commands', async t => { 37 | const result = await engine.runMultiple([getPath('firefox.cjs')], { 38 | scripts: { 39 | uri: 'document.documentURI' 40 | } 41 | }); 42 | 43 | t.deepEqual( 44 | result[0].browserScripts[0].scripts.uri, 45 | 'http://127.0.0.1:3000/simple/' 46 | ); 47 | }); 48 | -------------------------------------------------------------------------------- /test/commandtests/measureTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine(); 28 | return engine.start(); 29 | }); 30 | 31 | serial('Measure two urls after each other', async t => { 32 | const result = await engine.runMultiple([getPath('measure.cjs')], { 33 | scripts: { uri: 'document.documentURI' } 34 | }); 35 | t.deepEqual( 36 | result[0].browserScripts[0].scripts.uri, 37 | 'http://127.0.0.1:3000/simple/' 38 | ); 39 | t.deepEqual( 40 | result[1].browserScripts[0].scripts.uri, 41 | 'http://127.0.0.1:3000/dimple/' 42 | ); 43 | }); 44 | 45 | serial('Give each URL an alias', async t => { 46 | const result = await engine.runMultiple([getPath('measureAlias.cjs')], { 47 | scripts: { uri: 'document.documentURI' } 48 | }); 49 | t.deepEqual(result[0].info.alias, 'url1'); 50 | t.deepEqual(result[1].info.alias, 'url2'); 51 | }); 52 | -------------------------------------------------------------------------------- /test/commandtests/miscTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine(); 28 | return engine.start(); 29 | }); 30 | 31 | serial('Run misc commands', async t => { 32 | const result = await engine.runMultiple([getPath('misc.cjs')], { 33 | scripts: { uri: 'document.documentURI' } 34 | }); 35 | t.deepEqual( 36 | result[0].browserScripts[0].scripts.uri, 37 | 'http://127.0.0.1:3000/simple/' 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /test/commandtests/scrollTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine(); 28 | return engine.start(); 29 | }); 30 | 31 | serial('Scroll by pixel command', async t => { 32 | await engine.runMultiple([getPath('scrollByPixel.cjs')], { 33 | scripts: { uri: 'document.documentURI' } 34 | }); 35 | t.pass(); 36 | }); 37 | 38 | serial('Scroll to bottom command', async t => { 39 | await engine.runMultiple([getPath('scrollToBottom.cjs')], { 40 | scripts: { uri: 'document.documentURI' } 41 | }); 42 | t.pass(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/commandtests/stopWatch.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine(); 28 | return engine.start(); 29 | }); 30 | 31 | serial('Run stop watch commands', async t => { 32 | const result = await engine.runMultiple([getPath('stopWatch.cjs')], { 33 | scripts: { uri: 'document.documentURI' } 34 | }); 35 | t.true(result[0].extras[0].Measured_page > 0); 36 | }); 37 | -------------------------------------------------------------------------------- /test/commandtests/unified.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const { before, after, serial } = test; 3 | import { getEngine } from '../util/engine.js'; 4 | import { startServer, stopServer } from '../util/httpserver.js'; 5 | import { fileURLToPath } from 'node:url'; 6 | import path from 'node:path'; 7 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 8 | 9 | const timeout = 20_000; 10 | 11 | let engine; 12 | 13 | function getPath(file) { 14 | return path.resolve(__dirname, '..', 'data', 'commandscripts', file); 15 | } 16 | 17 | before('Setup the HTTP server', () => { 18 | return startServer(); 19 | }); 20 | 21 | after.always('Stop the HTTP server', () => { 22 | return stopServer(); 23 | }); 24 | 25 | serial.beforeEach('Start the browser', async t => { 26 | t.timeout(timeout); 27 | engine = getEngine(); 28 | return engine.start(); 29 | }); 30 | 31 | serial('Run unified scripts', async t => { 32 | const result = await engine.runMultiple([getPath('unified.cjs')], { 33 | scripts: { uri: 'document.documentURI' } 34 | }); 35 | t.deepEqual( 36 | result[0].browserScripts[0].scripts.uri, 37 | 'http://127.0.0.1:3000/simple/' 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /test/data/browserscripts/testscripts/empty.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/test/data/browserscripts/testscripts/empty.txt -------------------------------------------------------------------------------- /test/data/browserscripts/testscripts/scriptTags.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | return document.getElementsByTagName('script').length; 3 | })(); 4 | -------------------------------------------------------------------------------- /test/data/commandscripts/actions.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 3 | const clickable = await commands.element.getById('clickable'); 4 | return commands.action.getActions() 5 | .move({ origin: clickable }) 6 | .pause(1000) 7 | .press() 8 | .pause(1000) 9 | .sendKeys('abc') 10 | .perform(); 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /test/data/commandscripts/chrome.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | const responses = []; 3 | await commands.cdp.on('Network.responseReceived', params => { 4 | responses.push(params); 5 | }); 6 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 7 | await commands.cdp.send('Network.clearBrowserCookies'); 8 | await commands.trace.start(); 9 | await commands.measure.start('http://127.0.0.1:3000/dimple/'); 10 | await commands.trace.stop(); 11 | 12 | return commands.cdp.sendAndGet('Memory.getDOMCounters'); 13 | }; 14 | -------------------------------------------------------------------------------- /test/data/commandscripts/clickAndMeasure.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.navigate('http://127.0.0.1:3000/simple/'); 3 | await commands.measure.start('dimple'); 4 | await commands.click.byLinkTextAndWait('Dimple'); 5 | return commands.measure.stop(); 6 | }; 7 | -------------------------------------------------------------------------------- /test/data/commandscripts/clickBackAndForth.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.navigate('http://127.0.0.1:3000/simple/'); 3 | await commands.click.byPartialLinkTextAndWait('Dim'); 4 | await commands.click.bySelectorAndWait('body > p:nth-child(4) > a'); 5 | await commands.click.byXpathAndWait('/html/body/p[2]/a'); 6 | await commands.measure.start('simple'); 7 | await commands.click.byLinkTextAndWait('Search'); 8 | return commands.measure.stop(); 9 | }; 10 | -------------------------------------------------------------------------------- /test/data/commandscripts/firefox.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | 3 | const params = { 4 | method: 'script.addPreloadScript', 5 | params: { 6 | functionDeclaration: 'function(){alert("hepp");}' 7 | } 8 | }; 9 | 10 | await commands.bidi.send(params); 11 | 12 | await commands.profiler.start(); 13 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 14 | return commands.profiler.stop(); 15 | }; 16 | -------------------------------------------------------------------------------- /test/data/commandscripts/measure.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 3 | return commands.measure.start('http://127.0.0.1:3000/dimple/'); 4 | }; 5 | -------------------------------------------------------------------------------- /test/data/commandscripts/measureAlias.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('http://127.0.0.1:3000/simple/', 'url1'); 3 | return commands.measure.start('http://127.0.0.1:3000/dimple/', 'url2'); 4 | }; 5 | -------------------------------------------------------------------------------- /test/data/commandscripts/misc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.navigate('http://127.0.0.1:3000/search/'); 3 | await commands.addText.byId('grafana', 'search-input'); 4 | await commands.set.innerTextById('grafana2', 'search-input'); 5 | await commands.set.innerText('grafana3', '#search-input'); 6 | await commands.wait.byTime(500); 7 | await commands.js.run('document.body.style.backgroundColor="red"'); 8 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 9 | await commands.navigation.back({ wait: true }); 10 | await commands.navigation.forward({ wait: true }); 11 | await commands.navigation.refresh({ wait: true }); 12 | 13 | await commands.switch.toNewTab('http://127.0.0.1:3000/simple/'); 14 | return commands.switch.toNewWindow('http://127.0.0.1:3000/dimple/'); 15 | }; 16 | -------------------------------------------------------------------------------- /test/data/commandscripts/mouse.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.navigate('http://127.0.0.1:3000/simple/'); 3 | await commands.wait.byTime(1000); 4 | await commands.mouse.contextClick.byXpath('//html/body/p[2]/a'); 5 | await commands.wait.byTime(5000); 6 | 7 | await commands.navigate('http://127.0.0.1:3000/simple/'); 8 | await commands.wait.byTime(1000); 9 | await commands.mouse.singleClick.byXpath('/html/body/p[2]/a'); 10 | await commands.wait.byTime(5000); 11 | 12 | await commands.navigate('http://127.0.0.1:3000/simple/'); 13 | await commands.mouse.singleClick.bySelector('body > p:nth-child(4) > a'); 14 | await commands.wait.byTime(5000); 15 | 16 | await commands.navigate('http://127.0.0.1:3000/simple/'); 17 | await commands.mouse.doubleClick.bySelector('body > p:nth-child(4) > a'); 18 | await commands.wait.byTime(5000); 19 | 20 | await commands.navigate('http://127.0.0.1:3000/simple/'); 21 | await commands.mouse.mouseMove.bySelector('body > p:nth-child(4) > a'); 22 | await commands.mouse.mouseMove.byXpath('/html/body/p[2]/a'); 23 | return commands.mouse.mouseMove.toPosition(1, 1); 24 | }; 25 | -------------------------------------------------------------------------------- /test/data/commandscripts/scrollByPixel.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('scroll'); 3 | await commands.navigate('http://127.0.0.1:3000/simple/'); 4 | 5 | await commands.scroll.toBottom(20); 6 | return commands.measure.stop(); 7 | }; 8 | -------------------------------------------------------------------------------- /test/data/commandscripts/scrollToBottom.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('scroll'); 3 | await commands.navigate('http://127.0.0.1:3000/simple/'); 4 | await commands.wait.byTime(1000); 5 | 6 | await commands.wait.byTime(50); 7 | 8 | for (let i = 0; i < 10; i++) { 9 | await commands.scroll.byPixels(0, 200); 10 | await commands.wait.byTime(50); 11 | } 12 | 13 | await commands.wait.byTime(1000); 14 | return commands.measure.stop(); 15 | }; 16 | -------------------------------------------------------------------------------- /test/data/commandscripts/stopWatch.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | const before = commands.stopWatch.get('Before_navigating_page'); 3 | await commands.navigate('http://127.0.0.1:3000/simple/'); 4 | before.stop(); 5 | const measure = commands.stopWatch.get('Measured_page'); 6 | await commands.measure.start('http://127.0.0.1:3000/dimple/'); 7 | return measure.stopAndAdd(); 8 | }; 9 | -------------------------------------------------------------------------------- /test/data/commandscripts/unified.cjs: -------------------------------------------------------------------------------- 1 | async function setUp(context) { 2 | context.log.info('setUp example!'); 3 | } 4 | 5 | async function test(context, commands) { 6 | context.log.info('Test with setUp/tearDown example!'); 7 | return commands.measure.start('http://127.0.0.1:3000/simple/'); 8 | } 9 | 10 | async function tearDown(context) { 11 | context.log.info('tearDown example!'); 12 | } 13 | 14 | module.exports = { 15 | setUp: setUp, 16 | tearDown: tearDown, 17 | test: test 18 | }; 19 | -------------------------------------------------------------------------------- /test/data/exampleConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "iterations" : 1 3 | } -------------------------------------------------------------------------------- /test/data/html/css/main.css: -------------------------------------------------------------------------------- 1 | body{ 2 | color: #B24926; 3 | } 4 | -------------------------------------------------------------------------------- /test/data/html/dimple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This is a test site 5 | 6 | 7 | 8 |

Hello!

9 |

This is a paragraph, we load one CSS, one JavaScript and one image.

10 |

Simple

11 |

Search

12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/data/html/img/pirate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/test/data/html/img/pirate.png -------------------------------------------------------------------------------- /test/data/html/search/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Search 4 | 5 | 6 |

Super search

7 | 8 | 9 | -------------------------------------------------------------------------------- /test/data/html/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | This is a super simple test page 4 | 5 | 6 |

Welcome to the super simple and fast test page

7 |

It will be hard to create a faster page than this page.

8 |

Dimple

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/data/navigationscript/measure.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | context.log.info('Running script navigation'); 3 | return commands.measure.start('http://127.0.0.1:3000/simple/'); 4 | }; 5 | -------------------------------------------------------------------------------- /test/data/navigationscript/multi.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 3 | await commands.measure.start('http://127.0.0.1:3000/dimple/'); 4 | return commands.measure.start('http://127.0.0.1:3000/search/'); 5 | }; 6 | -------------------------------------------------------------------------------- /test/data/navigationscript/multiMac.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('https://www.sitespeed.io'); 3 | return commands.measure.start('https://www.sitespeed.io/documentation/'); 4 | }; 5 | -------------------------------------------------------------------------------- /test/data/navigationscript/navigateAndStartInTwoSteps.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.navigate('http://127.0.0.1:3000/simple/'); 3 | // we fetch the selenium webdriver from context 4 | const webdriver = context.selenium.webdriver; 5 | const driver = context.selenium.driver; 6 | // and get hold of some goodies we want to use 7 | const until = webdriver.until; 8 | const By = webdriver.By; 9 | 10 | const docLink = driver.findElement(By.linkText('Dimple')); 11 | // before you start, make your username and password 12 | await commands.measure.start(); 13 | // Before we click on the link, start the measurement 14 | docLink.click(); 15 | await driver.wait(until.elementLocated(By.linkText('Simple')), 6000); 16 | return commands.measure.stop(); 17 | }; 18 | -------------------------------------------------------------------------------- /test/data/navigationscript/sameURLTwice.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 3 | await commands.navigate('about:blank'); 4 | return commands.measure.start('http://127.0.0.1:3000/simple/'); 5 | }; 6 | -------------------------------------------------------------------------------- /test/data/navigationscript/sameURLTwiceWithClick.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | await commands.measure.start('http://127.0.0.1:3000/simple/'); 3 | await commands.measure.start('http://127.0.0.1:3000/dimple/'); 4 | // Hide everything 5 | // We do not hide the body since the body needs to be visible when we do the magic to find the staret of the 6 | // navigation by adding a layer of orange on top of the page 7 | await commands.js.run( 8 | 'for (let node of document.body.childNodes) { if (node.style) node.style.display = "none";}' 9 | ); 10 | // Start measurning 11 | await commands.measure.start(); 12 | // Click on the link for /simple/ and wait on navigation to happen 13 | await commands.click.byLinkText('Simple'); 14 | 15 | return commands.measure.stop(); 16 | }; 17 | -------------------------------------------------------------------------------- /test/data/navigationscript/simple.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | return commands.measure.start('https://www.sitespeed.io'); 3 | }; 4 | -------------------------------------------------------------------------------- /test/data/pagecompletescripts/pageComplete10sec.js: -------------------------------------------------------------------------------- 1 | return (function() { 2 | try { 3 | var end = window.performance.timing.loadEventEnd; 4 | return (end > 0) && (Date.now() > end + 10000); 5 | } catch(e) { 6 | return true; 7 | } 8 | })(); -------------------------------------------------------------------------------- /test/data/prepostscripts/postSample.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context) { 2 | context.log.info('Post script example!'); 3 | }; 4 | -------------------------------------------------------------------------------- /test/data/prepostscripts/preSample.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context) { 2 | context.log.info('Pre script example!'); 3 | }; 4 | -------------------------------------------------------------------------------- /test/data/scripting/common.cjs: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | return commands.measure.start('https://www.sitespeed.io'); 3 | }; -------------------------------------------------------------------------------- /test/data/scripting/common.js: -------------------------------------------------------------------------------- 1 | module.exports = async function (context, commands) { 2 | return commands.measure.start('https://www.sitespeed.io'); 3 | }; -------------------------------------------------------------------------------- /test/data/scripting/module.js: -------------------------------------------------------------------------------- 1 | export default async function (context, commands) { 2 | return commands.measure.start('https://www.sitespeed.io'); 3 | }; -------------------------------------------------------------------------------- /test/data/scripting/module.mjs: -------------------------------------------------------------------------------- 1 | export default async function (context, commands) { 2 | return commands.measure.start('https://www.sitespeed.io'); 3 | }; -------------------------------------------------------------------------------- /test/data/timings.json: -------------------------------------------------------------------------------- 1 | { 2 | "timings" : 3 | { 4 | "userTimings": 5 | { 6 | "marks": [ 7 | { 8 | "name": "foo_test", 9 | "startTime": "1500.111" 10 | }, 11 | { 12 | "name": "bar_test", 13 | "startTime": "1200.111" 14 | }, 15 | { 16 | "name": "foo_test2", 17 | "startTime": "1100.111" 18 | } 19 | ], 20 | "measures": [ 21 | { 22 | "name": "foo_test3", 23 | "startTime": "1500.111" 24 | }, 25 | { 26 | "name": "bar_test2", 27 | "startTime": "1200.111" 28 | }, 29 | { 30 | "name": "foo_test4", 31 | "startTime": "1100.111" 32 | } 33 | ] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /test/unittests/browserScriptsTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { 3 | findAndParseScripts, 4 | allScriptCategories, 5 | getScriptsForCategories 6 | } from '../../lib/support/browserScript.js'; 7 | import { fileURLToPath } from 'node:url'; 8 | import path from 'node:path'; 9 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 10 | 11 | const TEST_SCRIPTS_FOLDER = path.resolve( 12 | __dirname, 13 | '..', 14 | 'data', 15 | 'browserscripts', 16 | 'testscripts' 17 | ); 18 | 19 | test(`Parse valid scripts`, async t => { 20 | const scriptsByCategory = await findAndParseScripts( 21 | TEST_SCRIPTS_FOLDER, 22 | 'custom' 23 | ); 24 | 25 | const categoryNames = Object.keys(scriptsByCategory); 26 | 27 | t.deepEqual(categoryNames, ['testscripts']); 28 | 29 | const testscriptsCategory = scriptsByCategory.testscripts; 30 | const scriptNames = Object.keys(testscriptsCategory); 31 | 32 | t.deepEqual(scriptNames, ['scriptTags']); 33 | 34 | t.notDeepEqual(testscriptsCategory.script, ''); 35 | }); 36 | 37 | test(`Get scripts for all categories`, async t => { 38 | const categories = await allScriptCategories(); 39 | const scriptsByCategory = await getScriptsForCategories(categories); 40 | 41 | const categoryNames = Object.keys(scriptsByCategory); 42 | t.deepEqual(categoryNames, ['browser', 'pageinfo', 'timings']); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unittests/trafficShapingTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | 3 | import { 4 | getProfiles, 5 | parseTrafficShapeConfig 6 | } from '../../lib/connectivity/trafficShapeParser.js'; 7 | 8 | test(`parseTrafficShapeConfig`, async t => { 9 | let profiles = getProfiles(); 10 | 11 | for (const name of Object.keys(profiles)) { 12 | const profile = profiles[name]; 13 | const shapeConfig = parseTrafficShapeConfig({ 14 | connectivity: { profile: name } 15 | }); 16 | t.deepEqual(shapeConfig, profile, 'should return profile for ' + name); 17 | } 18 | 19 | let shapeConfig = parseTrafficShapeConfig({ 20 | connectivity: { profile: 'native' } 21 | }); 22 | t.is(shapeConfig, undefined, 'Return null for "native" traffic shape config'); 23 | 24 | shapeConfig = parseTrafficShapeConfig({}); 25 | t.is( 26 | shapeConfig, 27 | undefined, 28 | 'Return undefined if traffic shape config is missing' 29 | ); 30 | }); 31 | -------------------------------------------------------------------------------- /test/unittests/userTimingTest.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { filterAllowlisted } from '../../lib/support/userTiming.js'; 3 | import { readFileSync } from 'node:fs'; 4 | import { fileURLToPath } from 'node:url'; 5 | import path from 'node:path'; 6 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 | 8 | const timingsFile = path.resolve(__dirname, '..', 'data', 'timings.json'); 9 | 10 | test(`Filter white listed user timings`, async t => { 11 | const userTimings = JSON.parse(readFileSync(timingsFile, 'utf8')).timings 12 | .userTimings; 13 | filterAllowlisted(userTimings, 'foo_'); 14 | t.deepEqual( 15 | JSON.stringify(userTimings.marks), 16 | '[{"name":"foo_test","startTime":"1500.111"},{"name":"foo_test2","startTime":"1100.111"}]' 17 | ); 18 | 19 | t.deepEqual( 20 | JSON.stringify(userTimings.measures), 21 | '[{"name":"foo_test3","startTime":"1500.111"},{"name":"foo_test4","startTime":"1100.111"}]' 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /test/util/engine.js: -------------------------------------------------------------------------------- 1 | import { Engine } from '../../lib/core/engine/index.js'; 2 | import merge from 'lodash.merge'; 3 | 4 | export function getEngine(options) { 5 | const defaultOptions = { 6 | browser: process.env.BROWSER || 'chrome', 7 | timeouts: { 8 | browserStart: 60_000, 9 | scripts: 5000, 10 | pageLoad: 10_000, 11 | pageCompleteCheck: 5000 12 | }, 13 | iterations: 1, 14 | pageLoadStrategy: 'normal', 15 | pageCompleteWaitTime: 10, 16 | headless: true 17 | }; 18 | const o = merge({}, defaultOptions, options); 19 | return new Engine(o); 20 | } 21 | -------------------------------------------------------------------------------- /test/util/httpserver.js: -------------------------------------------------------------------------------- 1 | import handler from 'serve-handler'; 2 | import { createServer } from 'node:http'; 3 | 4 | let server; 5 | const port = 3000; 6 | 7 | export async function startServer() { 8 | server = createServer((request, response) => { 9 | return handler(request, response, { public: './test/data/html/' }); 10 | }); 11 | 12 | return server.listen(port, () => {}); 13 | } 14 | export async function stopServer() { 15 | return server.close(); 16 | } 17 | -------------------------------------------------------------------------------- /test/util/setup.js: -------------------------------------------------------------------------------- 1 | import { configureLog } from '@sitespeed.io/log'; 2 | configureLog({ silent: true }); 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "lib/core/engine/command/**/*", 4 | "lib/core/engine/context.js", 5 | "lib/core/engine/commands.js" 6 | ], 7 | "compilerOptions": { 8 | "allowJs": true, 9 | "declaration": true, 10 | "emitDeclarationOnly": true, 11 | "outDir": "types", 12 | "declarationMap": true, 13 | "esModuleInterop": true 14 | } 15 | } -------------------------------------------------------------------------------- /types/android/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/android/index.js"],"names":[],"mappings":"AAglBA,2DAWC;AA5kBD;IACE,0BAwBC;IAfC,YAAgC;IAEhC,QAIC;IACD,UAAgC;IAGhC,6BAA6B;IAC7B,yBAA2B;IAC3B,eAAgC;IAKlC,uBAgBC;IALC,YAA4C;IAG1C,YAAoE;IAIxE,wCAEC;IAED,8CAIC;IAED,6CAIC;IAED,2CAUC;IAED,mEAYC;IAED,mEAcC;IAED,uCAEC;IAED,0CAGC;IAED,4CAMC;IAED,4CAMC;IAED,uBAGC;IAED,kCAOC;IAED;;;;;;;OAuBC;IAED,2CAKC;IAED,8BAKC;IAED,oKAuBC;IAED,2BAIC;IAED,qCAGC;IAED,iCAGC;IAED,wBASC;IAED,2CAOC;IAED,gCAEC;IAED,0BAIC;IAED,iCAGC;IAED,8CAIC;IAED,4BAEC;IAED,sCAYC;IAED,mDAsBC;IAED,gDA+CC;IAED;;;;;;;OAqDC;IAED,4DAQC;IAED,mCAcC;IAED,kCAQC;IAED,iCAIC;IAED;;;;OAGC;IAED;;;OAEC;IAED,6GAaC;CACF"} -------------------------------------------------------------------------------- /types/chrome/parseCpuTrace.d.ts: -------------------------------------------------------------------------------- 1 | export function parseCPUTrace(tracelog: any, url: any): Promise<{ 2 | categories: { 3 | parseHTML: number; 4 | styleLayout: number; 5 | paintCompositeRender: number; 6 | scriptParseCompile: number; 7 | scriptEvaluation: number; 8 | garbageCollection: number; 9 | other: number; 10 | }; 11 | events: {}; 12 | urls: { 13 | url: string; 14 | value: number; 15 | }[]; 16 | } | { 17 | categories?: undefined; 18 | events?: undefined; 19 | urls?: undefined; 20 | }>; 21 | //# sourceMappingURL=parseCpuTrace.d.ts.map -------------------------------------------------------------------------------- /types/chrome/parseCpuTrace.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"parseCpuTrace.d.ts","sourceRoot":"","sources":["../../lib/chrome/parseCpuTrace.js"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;GA4EC"} -------------------------------------------------------------------------------- /types/chrome/traceCategoriesParser.d.ts: -------------------------------------------------------------------------------- 1 | export function parse(events: any, url: any): any; 2 | //# sourceMappingURL=traceCategoriesParser.d.ts.map -------------------------------------------------------------------------------- /types/chrome/traceCategoriesParser.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"traceCategoriesParser.d.ts","sourceRoot":"","sources":["../../lib/chrome/traceCategoriesParser.js"],"names":[],"mappings":"AA2JA,kDAQC"} -------------------------------------------------------------------------------- /types/chrome/webdriver/traceUtilities.d.ts: -------------------------------------------------------------------------------- 1 | export function getRenderBlocking(trace: any): Promise<{ 2 | renderBlockingInfo: {}; 3 | renderBlocking: { 4 | recalculateStyle: {}; 5 | requests: {}; 6 | }; 7 | }>; 8 | //# sourceMappingURL=traceUtilities.d.ts.map -------------------------------------------------------------------------------- /types/chrome/webdriver/traceUtilities.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"traceUtilities.d.ts","sourceRoot":"","sources":["../../../lib/chrome/webdriver/traceUtilities.js"],"names":[],"mappings":"AA8DA;;;;;;GAoCC"} -------------------------------------------------------------------------------- /types/core/engine/command/action.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/action.js"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH;IACE,0BASC;IARC;;OAEG;IACH,eAAiC;IACjC;;OAEG;IACH,gBAAmD;IASrD,sBAEC;IAED;;;;;;;;;;;;OAYG;IACH,aAVa,eAAe,CAY3B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,QAAQ,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,mBAHW,MAAM,GACJ,QAAQ,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,yBAHW,MAAM,GACJ,QAAQ,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,iCAHW,MAAM,GACJ,QAAQ,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,uBAHW,MAAM,GACJ,QAAQ,UAAU,CAAC,CAI/B;CACF;2CAnG0D,oBAAoB;2BAApB,oBAAoB"} -------------------------------------------------------------------------------- /types/core/engine/command/actions.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class provides an abstraction layer for Selenium's action sequence functionality. 3 | * It allows for easy interaction with web elements using different locating strategies 4 | * and simulating complex user gestures like mouse movements, key presses, etc. 5 | * 6 | * @class 7 | * @hideconstructor 8 | * @see https://www.selenium.dev/documentation/webdriver/actions_api/ 9 | * @see https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html 10 | */ 11 | export class Actions { 12 | constructor(browser: any); 13 | /** 14 | * @private 15 | */ 16 | private driver; 17 | /** 18 | * @private 19 | */ 20 | private actions; 21 | clear(): Promise; 22 | /** 23 | * Retrieves the current action sequence builder. 24 | * The actions builder can be used to chain multiple browser actions. 25 | * @returns {SeleniumActions} The current Selenium Actions builder object for chaining browser actions. 26 | * @example 27 | * // Example of using the actions builder to perform a drag-and-drop operation: 28 | * const elementToDrag = await commands.element.getByCss('.draggable'); 29 | * const dropTarget = await commands.element.getByCss('.drop-target'); 30 | * await commands.action.getAction() 31 | * .dragAndDrop(elementToDrag, dropTarget) 32 | * .perform(); 33 | * 34 | */ 35 | getActions(): SeleniumActions; 36 | } 37 | import { Actions as SeleniumActions } from 'selenium-webdriver/lib/input.js'; 38 | //# sourceMappingURL=actions.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/actions.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/actions.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH;IACE,0BASC;IARC;;OAEG;IACH,eAAiC;IACjC;;OAEG;IACH,gBAAmD;IASrD,sBAEC;IAED;;;;;;;;;;;;OAYG;IACH,cAVa,eAAe,CAY3B;CACF;2CAjD0C,iCAAiC"} -------------------------------------------------------------------------------- /types/core/engine/command/addText.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"addText.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/addText.js"],"names":[],"mappings":"AAIA;;;;GAIG;AACH;IACE,0BAKC;IAJC;;OAEG;IACH,gBAAsB;IAGxB;;;;;;;;;OASG;IACH,WALW,MAAM,MACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;;;;;;;OASG;IACH,cALW,MAAM,SACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;;;;;;;OASG;IACH,iBALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;;;;;;;OASG;IACH,kBALW,MAAM,aACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;;;;;;;OASG;IACH,aALW,MAAM,QACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAoBzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/android.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides functionality to interact with an Android device through shell commands. 3 | * @class 4 | * @see https://www.sitespeed.io/documentation/sitespeed.io/mobile-phones/#test-on-android 5 | * @hideconstructor 6 | */ 7 | export class AndroidCommand { 8 | constructor(options: any); 9 | /** 10 | * @private 11 | */ 12 | private options; 13 | /** 14 | * Runs a shell command on the connected Android device. 15 | * This method requires the Android device to be properly configured. 16 | * 17 | * @async 18 | * @example await commands.android.shell(''); 19 | * @param {string} command - The shell command to run on the Android device. 20 | * @returns {Promise} A promise that resolves with the result of the command or rejects if there's an error. 21 | * @throws {Error} Throws an error if Android is not configured or if the command fails. 22 | */ 23 | shell(command: string): Promise; 24 | a: Android; 25 | /** 26 | * Runs a shell command on the connected Android device as the root user. 27 | * This method requires the Android device to be properly configured and that you 28 | * rooted the device. 29 | * 30 | * @async 31 | * @example await commands.android.shellAsRoot(''); 32 | * @param {string} command - The shell command to run on the Android device as root. 33 | * @returns {Promise} A promise that resolves with the result of the command or rejects if there's an error. 34 | * @throws {Error} Throws an error if Android is not configured or if the command fails. 35 | */ 36 | shellAsRoot(command: string): Promise; 37 | } 38 | import { Android } from '../../../android/index.js'; 39 | //# sourceMappingURL=android.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/android.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/android.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACE,0BAKC;IAJC;;OAEG;IACH,gBAAsB;IAGxB;;;;;;;;;OASG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAc3B;IAPK,WAAkC;IASxC;;;;;;;;;;OAUG;IACH,qBAJW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAgB3B;CACF;wBAhE4C,2BAA2B"} -------------------------------------------------------------------------------- /types/core/engine/command/bidi.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bidi.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/bidi.js"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH;IACE,mDASC;IARC;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,oBAA8B;IAGhC;;;;;;;;;OASG;IACH,sCAQC;IAED;;;;;OAKG;IACH,gBAHa,OAAO,KAAQ,CAS3B;IAED;;;;;;;;OAQG;IACH,uBAJW,MAAM,GACJ,OAAO,KAAQ,CAY3B;IAED;;;;;;;;OAQG;IACH,yBAJW,MAAM,GACH,OAAO,KAAQ,CAY5B;IAED;;;;;;;;;;;;;;;OAeG;IACH,uBAFa,OAAO,KAAQ,CAmB3B;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/cache.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Manage the browser cache. 3 | * This class provides methods to clear the cache and cookies in different browsers. 4 | * 5 | * @class 6 | * @hideconstructor 7 | */ 8 | export class Cache { 9 | constructor(browser: any, browserName: any, cdp: any); 10 | /** 11 | * @private 12 | */ 13 | private browser; 14 | /** 15 | * @private 16 | */ 17 | private browserName; 18 | /** 19 | * @private 20 | */ 21 | private cdp; 22 | /** 23 | * Clears the browser cache. This includes both cache and cookies. 24 | * 25 | * For Chrome and Edge, it uses the Chrome DevTools Protocol (CDP) commands. 26 | * If the browser is not supported, logs an error message. 27 | * 28 | * @async 29 | * @example await commands.cache.clear(); 30 | * @throws Will throw an error if the browser is not supported. 31 | * @returns {Promise} A promise that resolves when the cache and cookies are cleared. 32 | */ 33 | clear(): Promise; 34 | /** 35 | * Clears the browser cache while keeping the cookies. 36 | * 37 | * For Chrome and Edge, it uses the Chrome DevTools Protocol (CDP) command to clear the cache. 38 | * If the browser is not supported, logs an error message. 39 | * 40 | * @async 41 | * @example await commands.cache.clearKeepCookies(); 42 | * @throws Will throw an error if the browser is not supported. 43 | * @returns {Promise} A promise that resolves when the cache is cleared but cookies are kept. 44 | */ 45 | clearKeepCookies(): Promise; 46 | } 47 | //# sourceMappingURL=cache.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/cache.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/cache.js"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH;IACE,sDAaC;IAZC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,oBAA8B;IAC9B;;OAEG;IACH,YAAc;IAGhB;;;;;;;;;;OAUG;IACH,SAFa,OAAO,CAAC,IAAI,CAAC,CAUzB;IAED;;;;;;;;;;OAUG;IACH,oBAFa,OAAO,CAAC,IAAI,CAAC,CASzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/chromeDevToolsProtocol.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"chromeDevToolsProtocol.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/chromeDevToolsProtocol.js"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH;IACE,mDASC;IARC;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,oBAA8B;IAGhC;;;;;;;OAOG;IACH,UAJW,MAAM,8BAuBhB;IAED;;;;;;;;;;OAUG;IACH,oBALW,MAAM,oBAGJ,OAAO,KAAQ,CAsB3B;IAED;;;;;;OAMG;IACH,oBAMC;IAED;;;;;;;;;;OAUG;IACH,cALW,MAAM,oBAGJ,OAAO,CAAC,IAAI,CAAC,CAmBzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/chromeTrace.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages Chrome trace functionality, enabling custom profiling and trace collection in Chrome. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class ChromeTrace { 8 | constructor(engineDelegate: any, index: any, options: any, result: any); 9 | /** 10 | * @private 11 | */ 12 | private engineDelegate; 13 | /** 14 | * @private 15 | */ 16 | private options; 17 | /** 18 | * @private 19 | */ 20 | private result; 21 | /** 22 | * @private 23 | */ 24 | private index; 25 | /** 26 | * Starts the Chrome trace collection. 27 | * 28 | * @async 29 | * @example await commands.trace.start(); 30 | * @returns {Promise} A promise that resolves when tracing is started. 31 | * @throws {Error} Throws an error if not running Chrome or if configuration is not set for custom tracing. 32 | */ 33 | start(): Promise; 34 | /** 35 | * Stops the Chrome trace collection, processes the collected data, and attaches it to the result object. 36 | * 37 | * @async 38 | * @example await commands.trace.stop(); 39 | * @returns {Promise} A promise that resolves when tracing is stopped and data is processed. 40 | * @throws {Error} Throws an error if not running Chrome or if custom tracing was not started. 41 | */ 42 | stop(): Promise; 43 | events: any; 44 | } 45 | //# sourceMappingURL=chromeTrace.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/chromeTrace.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"chromeTrace.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/chromeTrace.js"],"names":[],"mappings":"AAMA;;;;;GAKG;AACH;IACE,wEAiBC;IAhBC;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,eAAoB;IACpB;;OAEG;IACH,cAAkB;IAGpB;;;;;;;OAOG;IACH,SAHa,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;;OAOG;IACH,QAHa,OAAO,CAAC,IAAI,CAAC,CAuCzB;IA/BK,YAAgB;CAgCvB"} -------------------------------------------------------------------------------- /types/core/engine/command/click.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"click.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/click.js"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH;IACE,kDASC;IARC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAG5C;;;;;;;OAOG;IACH,uBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,8BAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;IAED;;;;;;;OAOG;IACH,iBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,wBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,wBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,+BAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAczB;IACD;;;;;;;OAOG;IACH,sBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;;OAOG;IACH,SAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,gBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;IAED;;;;;;;OAOG;IACH,SAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,aAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;OAKG;IACH,gBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;IAED;;;;;;;OAOG;IACH,qBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,4BAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/debug.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides debugging capabilities within a browser automation script. 3 | * It allows setting breakpoints to pause script execution and inspect the current state. 4 | * 5 | * @class 6 | * @hideconstructor 7 | */ 8 | export class Debug { 9 | constructor(browser: any, options: any); 10 | /** 11 | * @private 12 | */ 13 | private browser; 14 | /** 15 | * @private 16 | */ 17 | private options; 18 | /** 19 | * Adds a breakpoint to the script. The browser will pause at the breakpoint, waiting for user input to continue. 20 | * This is useful for debugging and inspecting the browser state at a specific point in the script. 21 | * 22 | * @example await commands.debug.breakpoint('break'); 23 | * @async 24 | * @param {string} [name] - An optional name for the breakpoint for logging purposes. 25 | * @returns {Promise} A promise that resolves when the user chooses to continue from the breakpoint. 26 | */ 27 | breakpoint(name?: string): Promise; 28 | } 29 | //# sourceMappingURL=debug.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/debug.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/debug.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH;IACE,wCASC;IARC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,gBAAsB;IAGxB;;;;;;;;OAQG;IACH,kBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CA+BzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/element.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This class provides a way to get hokld of Seleniums WebElements. 3 | * @class 4 | * @hideconstructor 5 | */ 6 | export class Element { 7 | constructor(browser: any); 8 | /** 9 | * @private 10 | */ 11 | private driver; 12 | /** 13 | * Finds an element by its CSS selector. 14 | * 15 | * @param {string} name - The CSS selector of the element. 16 | * @returns {Promise} A promise that resolves to the WebElement found. 17 | */ 18 | getByCss(name: string): Promise; 19 | /** 20 | * Finds an element by its ID. 21 | * 22 | * @param {string} id - The ID of the element. 23 | * @returns {Promise} A promise that resolves to the WebElement found. 24 | */ 25 | getById(id: string): Promise; 26 | /** 27 | * Finds an element by its XPath. 28 | * 29 | * @param {string} xpath - The XPath query of the element. 30 | * @returns {Promise} A promise that resolves to the WebElement found. 31 | */ 32 | getByXpath(xpath: string): Promise; 33 | /** 34 | * Finds an element by its class name. 35 | * 36 | * @param {string} className - The class name of the element. 37 | * @returns {Promise} A promise that resolves to the WebElement found. 38 | */ 39 | getByClassName(className: string): Promise; 40 | /** 41 | * Finds an element by its name attribute. 42 | * 43 | * @param {string} name - The name attribute of the element. 44 | * @returns {Promise} A promise that resolves to the WebElement found. 45 | */ 46 | getByName(name: string): Promise; 47 | } 48 | import { WebElement } from 'selenium-webdriver'; 49 | //# sourceMappingURL=element.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/element.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/element.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH;IACE,0BAKC;IAJC;;OAEG;IACH,eAAiC;IAGnC;;;;;OAKG;IACH,eAHW,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,YAHW,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,0BAHW,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAI/B;IAED;;;;;OAKG;IACH,gBAHW,MAAM,GACJ,OAAO,CAAC,UAAU,CAAC,CAI/B;CACF;2BA/D8B,oBAAoB"} -------------------------------------------------------------------------------- /types/core/engine/command/geckoProfiler.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages the Gecko Profiler for profiling Firefox performance. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class GeckoProfiler { 8 | constructor(GeckoProfiler: any, browser: any, index: any, options: any, result: any); 9 | /** 10 | * @private 11 | */ 12 | private GeckoProfiler; 13 | /** 14 | * @private 15 | */ 16 | private browser; 17 | /** 18 | * @private 19 | */ 20 | private index; 21 | /** 22 | * @private 23 | */ 24 | private options; 25 | /** 26 | * @private 27 | */ 28 | private result; 29 | /** 30 | * Starts the Gecko Profiler. 31 | * 32 | * @async 33 | * @returns {Promise} A promise that resolves when the profiler is started. 34 | * @throws {Error} Throws an error if not running Firefox or if the configuration is not set for custom profiling. 35 | */ 36 | start(): Promise; 37 | /** 38 | * Stops the Gecko Profiler and processes the collected data. 39 | * 40 | * @async 41 | * @returns {Promise} A promise that resolves when the profiler is stopped and the data is processed. 42 | * @throws {Error} Throws an error if not running Firefox or if custom profiling was not started. 43 | */ 44 | stop(): Promise; 45 | } 46 | //# sourceMappingURL=geckoProfiler.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/geckoProfiler.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"geckoProfiler.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/geckoProfiler.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH;IACE,qFAqBC;IApBC;;OAEG;IACH,sBAAkC;IAClC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,cAAkB;IAClB;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,eAAoB;IAGtB;;;;;;OAMG;IACH,SAHa,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;OAMG;IACH,QAHa,OAAO,CAAC,IAAI,CAAC,CAezB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/javaScript.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"javaScript.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/javaScript.js"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH;IACE,kDASC;IARC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAG5C;;;;;;;OAOG;IACH,QAJW,MAAM,GACJ,OAAO,CAAC,GAAC,CAAC,CAYtB;IAED;;;;;;;OAOG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,GAAC,CAAC,CAYtB;IAED;;;;;;;OAOG;IACH,kBAJW,MAAM,GACJ,OAAO,CAAC,GAAC,CAAC,CAetB;IAED;;;;;;;OAOG;IACH,yBAJW,MAAM,GACJ,OAAO,CAAC,GAAC,CAAC,CAYtB;IAED;;;;;;;OAOG;IACH,uBAJW,MAAM,GACJ,OAAO,CAAC,GAAC,CAAC,CAetB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/measure.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/measure.js"],"names":[],"mappings":"AAgCA;;;;;;GAMG;AACH;IACE,gQA+GC;IAhGC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAC1C;;OAEG;IACH,cAAkB;IAClB;;OAEG;IACH,eAAoB;IACpB;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAC1C;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,oBAAyD;IACzD;;OAEG;IACH,eAAoB;IACpB;;OAEG;IACH,0BAA0C;IAC1C;;OAEG;IACH,+BAAoD;IACpD;;OAEG;IACH,uBAAoC;IACpC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,8BAA8B;IAC9B;;OAEG;IACH,6BAA6B;IAC7B;;OAEG;IACH,uBAA2B;IAC3B;;OAEG;IACH,mBAAoB;IACpB;;OAEG;IACH,gBAA6D;IAC7D;;OAEG;IACH,2BAIC;IACD;;OAEG;IACH,uBAAsE;IACtE;;OAEG;IACH,2BAIC;IAGH;;;OAGG;IACH,oBAsBC;IArBC,aAAuE;IAuBzE;;;OAGG;IACH,mBAKC;IAED;;;OAGG;IACH,eAyBC;IAED;;;OAGG;IACH,iBAQC;IAED;;;;;;;;;;;;OAYG;IACH,kBAmBC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBALW,MAAM,kBACN,MAAM,GAEJ,OAAO,CAAC,IAAI,CAAC,CAmFzB;IAED;;;;;;;;OAQG;IACH,0BAJW,MAAM,gBAiBhB;IACD;;;;;;;;;OASG;IACH,qBAJW,MAAM,gBAyEhB;IAED;;;;;;;;OAQG;IACH,UAJW,MAAM,SACN,GAAC,QAWX;IAED;;;;;;;;;OASG;IACH,6BAOC;IAED;;;OAGG;IACH,gBAwLC;CACF;sBA7rBqB,yBAAyB"} -------------------------------------------------------------------------------- /types/core/engine/command/meta.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Add meta data to your user journey. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class Meta { 8 | /** 9 | * Sets the description for the user journey. 10 | * @example commands.meta.setDescription('My test'); 11 | * @param {string} text - The text to set as the description. 12 | */ 13 | setDescription(text: string): void; 14 | description: string; 15 | /** 16 | * Sets the title for the user journey. 17 | * @example commands.meta.setTitle('Test title'); 18 | * @param {string} text - The text to set as the title. 19 | */ 20 | setTitle(text: string): void; 21 | title: string; 22 | } 23 | //# sourceMappingURL=meta.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/meta.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/meta.js"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH;IAGE;;;;OAIG;IACH,qBAFW,MAAM,QAIhB;IADC,oBAAuB;IAGzB;;;;OAIG;IACH,eAFW,MAAM,QAIhB;IADC,cAAiB;CAEpB"} -------------------------------------------------------------------------------- /types/core/engine/command/mouse/clickAndHold.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"clickAndHold.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/mouse/clickAndHold.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH;IACE,0BASC;IARC;;OAEG;IACH,eAAiC;IACjC;;OAEG;IACH,gBAAmD;IAGrD;;;;;;;OAOG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAczB;IAED;;;;;;;OAOG;IACH,qBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;;OAMG;IACH,YAHa,OAAO,CAAC,IAAI,CAAC,CAWzB;IAED;;;;;;;;OAQG;IACH,iBALW,MAAM,QACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAgBzB;IAED;;;;;;;OAOG;IACH,sBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,4BAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,wBALW,MAAM,QACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAgBzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/mouse/contextClick.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides functionality to perform a context click (right-click) on elements in a web page. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class ContextClick { 8 | constructor(browser: any); 9 | /** 10 | * @private 11 | */ 12 | private driver; 13 | /** 14 | * @private 15 | */ 16 | private actions; 17 | /** 18 | * Performs a context click (right-click) on an element that matches a given XPath selector. 19 | * 20 | * @async 21 | * @param {string} xpath - The XPath selector of the element to context click. 22 | * @returns {Promise} A promise that resolves when the context click action is performed. 23 | * @throws {Error} Throws an error if the element is not found. 24 | */ 25 | byXpath(xpath: string): Promise; 26 | /** 27 | * Performs a context click (right-click) on an element that matches a given CSS selector. 28 | * 29 | * @async 30 | * @param {string} selector - The CSS selector of the element to context click. 31 | * @returns {Promise} A promise that resolves when the context click action is performed. 32 | * @throws {Error} Throws an error if the element is not found. 33 | */ 34 | bySelector(selector: string): Promise; 35 | /** 36 | * Performs a context click (right-click) at the current cursor position. 37 | * 38 | * @async 39 | * @returns {Promise} A promise that resolves when the context click action is performed. 40 | * @throws {Error} Throws an error if the context click action cannot be performed. 41 | */ 42 | atCursor(): Promise; 43 | } 44 | //# sourceMappingURL=contextClick.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/mouse/contextClick.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"contextClick.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/mouse/contextClick.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH;IACE,0BASC;IARC;;OAEG;IACH,eAAiC;IACjC;;OAEG;IACH,gBAAmD;IAGrD;;;;;;;OAOG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,qBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAczB;IAED;;;;;;OAMG;IACH,YAHa,OAAO,CAAC,IAAI,CAAC,CAWzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/mouse/doubleClick.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"doubleClick.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/mouse/doubleClick.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACE,kDAaC;IAZC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,gBAAgE;IAChE;;OAEG;IACH,0BAA0C;IAG5C;;;;;;;;OAQG;IACH,eALW,MAAM,kBAEJ,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;;;;OAQG;IACH,qBALW,MAAM,kBAEJ,OAAO,CAAC,IAAI,CAAC,CAmBzB;IAED;;;;;;;OAOG;IACH,yBAHa,OAAO,CAAC,IAAI,CAAC,CAczB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/mouse/index.d.ts: -------------------------------------------------------------------------------- 1 | export { ClickAndHold } from "./clickAndHold.js"; 2 | export { DoubleClick } from "./doubleClick.js"; 3 | export { ContextClick } from "./contextClick.js"; 4 | export { SingleClick } from "./singleClick.js"; 5 | export { MouseMove } from "./mouseMove.js"; 6 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/mouse/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/mouse/index.js"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /types/core/engine/command/mouse/mouseMove.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"mouseMove.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/mouse/mouseMove.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACE,0BASC;IARC;;OAEG;IACH,eAAiC;IACjC;;OAEG;IACH,gBAAmD;IAGrD;;;;;;;OAOG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,qBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,iBALW,MAAM,QACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAWzB;IAED;;;;;;;;OAQG;IACH,kBALW,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/mouse/singleClick.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"singleClick.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/mouse/singleClick.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH;IACE,kDAaC;IAZC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,gBAAgE;IAChE;;OAEG;IACH,0BAA0C;IAG5C;;;;;;;;OAQG;IACH,eALW,MAAM,kBAEJ,OAAO,CAAC,IAAI,CAAC,CAoBzB;IAED;;;;;;;OAOG;IACH,sBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;;;OAQG;IACH,qBALW,MAAM,kBAEJ,OAAO,CAAC,IAAI,CAAC,CAsBzB;IAED;;;;;;;OAOG;IACH,4BAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;;;OAOG;IACH,yBAHa,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;;;OAOG;IACH,mBAHa,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,iBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,wBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,wBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,+BAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,SAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;OAOG;IACH,gBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/navigation.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/navigation.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AAEH;IACE,kDASC;IARC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAG5C;;;;;;;;OAQG;IACH,qBAHa,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;;;OAQG;IACH,wBAHa,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;;;OAQG;IACH,wBAHa,OAAO,CAAC,IAAI,CAAC,CAezB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/perfStats.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages the PerfStats interface used for collecting Firefox performance counters. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class PerfStatsInterface { 8 | constructor(browser: any, options: any); 9 | /** 10 | * @private 11 | */ 12 | private PerfStats; 13 | /** 14 | * @private 15 | */ 16 | private options; 17 | /** 18 | * Starts PerfStats collection based on the given feature mask. 19 | * 20 | * @async 21 | * @returns {Promise} A promise that resolves when collection has started. 22 | * @throws {Error} Throws an error if not running Firefox. 23 | */ 24 | start(featureMask?: number): Promise; 25 | /** 26 | * Stops PerfStats collection. 27 | * 28 | * @async 29 | * @returns {Promise} A promise that resolves when collection has stopped. 30 | * @throws {Error} Throws an error if not running Firefox. 31 | */ 32 | stop(): Promise; 33 | /** 34 | * Returns an object that has cumulative perfstats statistics across each 35 | * process for the features that were enabled. Should be called before stop(). 36 | * 37 | * @async 38 | * @returns {Object} Returns an object with cumulative results. 39 | * @throws {Error} Throws an error if not running Firefox. 40 | */ 41 | collect(): any; 42 | } 43 | //# sourceMappingURL=perfStats.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/perfStats.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"perfStats.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/perfStats.js"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH;IACE,wCASC;IARC;;OAEG;IACH,kBAAuC;IACvC;;OAEG;IACH,gBAAsB;IAGxB;;;;;;OAMG;IAEH,6BAJa,OAAO,CAAC,IAAI,CAAC,CAUzB;IAED;;;;;;OAMG;IACH,QAHa,OAAO,CAAC,IAAI,CAAC,CASzB;IAED;;;;;;;OAOG;IACH,eAMC;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/screenshot.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Take a screenshot. The screenshot will be stored to disk, 3 | * named by the name provided to the take function. 4 | * 5 | * @class 6 | * @hideconstructor 7 | */ 8 | export class Screenshot { 9 | constructor(screenshotManager: any, browser: any, index: any); 10 | /** 11 | * @private 12 | */ 13 | private screenshotManager; 14 | /** 15 | * @private 16 | */ 17 | private browser; 18 | /** 19 | * @private 20 | */ 21 | private index; 22 | /** 23 | * Takes a screenshot and saves it using the screenshot manager. 24 | * 25 | * @async 26 | * @example async commands.screenshot.take('my_startpage'); 27 | * @param {string} name The name to assign to the screenshot file. 28 | * @throws {Error} Throws an error if the name parameter is not provided. 29 | * @returns {Promise} A promise that resolves with the screenshot details. 30 | */ 31 | take(name: string): Promise; 32 | } 33 | //# sourceMappingURL=screenshot.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/screenshot.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"screenshot.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/screenshot.js"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH;IACE,8DAaC;IAZC;;OAEG;IACH,0BAA0C;IAC1C;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,cAAkB;IAGpB;;;;;;;;OAQG;IAEH,WALW,MAAM,GAEJ,OAAO,KAAQ,CAa3B;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/scroll.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/scroll.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH;IACE,wCASC;IARC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,gBAAsB;IAGxB;;;;;;;OAOG;IACH,kBAJW,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAKzB;IAED;;;;;;;OAOG;IACH,eAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;OAMG;IACH,eAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;OAMG;IACH,qBAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAezB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/select.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/select.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACE,0BAKC;IAJC;;OAEG;IACH,gBAAsB;IAGxB;;;;;;;;OAQG;IACH,6BALW,MAAM,SACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,iCALW,MAAM,SACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAczB;IAED;;;;;;;;OAQG;IACH,6BALW,MAAM,SACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAgBzB;IAED;;;;;;;;OAQG;IACH,iCALW,MAAM,SACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAgBzB;IAED;;;;;;;OAOG;IACH,uBAJW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAazB;IAED;;;;;;;OAOG;IACH,wBAJW,MAAM,GACJ,OAAO,CAAC,MAAM,EAAE,CAAC,CAoB7B;IAED;;;;;;;OAOG;IACH,+BAJW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAa3B;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/set.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/set.js"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH;IACE,0BAKC;IAJC;;OAEG;IACH,gBAAsB;IAGxB;;;;;;;;OAQG;IACH,gBALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAczB;IAED;;;;;;;;OAQG;IACH,oBALW,MAAM,MACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,gBALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAczB;IAED;;;;;;;;OAQG;IACH,oBALW,MAAM,MACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,aALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;IAED;;;;;;;;OAQG;IACH,iBALW,MAAM,MACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAYzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/stopWatch.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A stopwatch utility for measuring time intervals. 3 | * 4 | * @class 5 | * @hideconstructor 6 | */ 7 | export class StopWatch { 8 | constructor(name: any, measure: any); 9 | /** 10 | * @private 11 | */ 12 | private name; 13 | /** 14 | * @private 15 | */ 16 | private measure; 17 | /** 18 | * Starts the stopwatch. 19 | */ 20 | start(): void; 21 | /** 22 | * Stops the stopwatch and automatically adds the measured time to the 23 | * last measured page. Logs an error if no page has been measured. 24 | * @returns {number} The measured time in milliseconds. 25 | */ 26 | stopAndAdd(): number; 27 | /** 28 | * Stops the stopwatch. 29 | * @returns {number} The measured time in milliseconds. 30 | */ 31 | stop(): number; 32 | /** 33 | * Gets the name of the stopwatch. 34 | * @returns {string} The name of the stopwatch. 35 | */ 36 | getName(): string; 37 | } 38 | /** 39 | * @private 40 | */ 41 | export class Watch { 42 | constructor(measure: any); 43 | measure: any; 44 | /** 45 | * @private 46 | */ 47 | private get; 48 | } 49 | //# sourceMappingURL=stopWatch.d.ts.map -------------------------------------------------------------------------------- /types/core/engine/command/stopWatch.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"stopWatch.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/stopWatch.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH;IACE,qCAaC;IAZC;;OAEG;IACH,aAAgB;IAChB;;OAEG;IACH,gBAAsB;IAOxB;;OAEG;IACH,cAEC;IAED;;;;OAIG;IACH,cAFa,MAAM,CAOlB;IAED;;;OAGG;IACH,QAFa,MAAM,CAMlB;IAED;;;OAGG;IACH,WAFa,MAAM,CAIlB;CACF;AAED;;GAEG;AACH;IACE,0BAEC;IADC,aAAsB;IAGxB;;OAEG;IACH,YAEC;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/switch.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"switch.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/switch.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH;IACE,iEAaC;IAZC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAC1C;;OAEG;IACH,iBAAwB;IAG1B;;;;;;OAMG;IACH,YAHW,MAAM,GAAC,MAAM,iBAYvB;IAED;;;;;;OAMG;IACH,sBAHW,MAAM,iBAkBhB;IAED;;;;;;OAMG;IACH,4BAHW,MAAM,iBAkBhB;IAED;;;;;;OAMG;IACH,eAHW,MAAM,iBAYhB;IAED;;;;;OAKG;IACH,+BASC;IAED;;;;;;OAMG;IACH,eAHW,MAAM,iBAiBhB;IAED;;;;;;OAMG;IACH,kBAHW,MAAM,iBAiBhB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/command/util/lcpHighlightScript.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"lcpHighlightScript.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/util/lcpHighlightScript.js"],"names":[],"mappings":"AAAA,o8DAwCE"} -------------------------------------------------------------------------------- /types/core/engine/command/util/lsHighlightScript.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"lsHighlightScript.d.ts","sourceRoot":"","sources":["../../../../../lib/core/engine/command/util/lsHighlightScript.js"],"names":[],"mappings":"AAAA,0+FAyEE"} -------------------------------------------------------------------------------- /types/core/engine/command/wait.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../../../lib/core/engine/command/wait.js"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH;IACE,kDASC;IARC;;OAEG;IACH,gBAAsB;IACtB;;OAEG;IACH,0BAA0C;IAG5C;;;;;;;;OAQG;IACH,SALW,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAiBzB;IAED;;;;;;;;OAQG;IACH,mBALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAmBzB;IAED;;;;;;;;OAQG;IACH,eALW,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAmBzB;IAED;;;;;;;;OAQG;IACH,yBALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAqBzB;IAED;;;;;;;;OAQG;IACH,qBALW,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAuBzB;IAED;;;;;;;;OAQG;IACH,+BALW,MAAM,YACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAwBzB;IAED;;;;;;;OAOG;IACH,WAHW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;;OAKG;IACH,oBAFa,OAAO,CAAC,IAAI,CAAC,CAIzB;IAED;;;;;;;;OAQG;IACH,0BALW,MAAM,WACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAiBzB;CACF"} -------------------------------------------------------------------------------- /types/core/engine/commands.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../lib/core/engine/commands.js"],"names":[],"mappings":"AA+BA;;;GAGG;AACH;IACE,gQAwPC;IAnNC,+BAMC;IAOD,8BAA0B;IAO1B;;;OAGG;IACH,OAFU,WAAW,CAE+C;IAEpE;;;OAGG;IACH,OAFU,KAAK,CAEmC;IAElD;;;OAGG;IACH,QAFU,MAAM,CAE0B;IAE1C;;;OAGG;IACH,SAFU,OAAO,CAEkB;IAEnC;;;OAGG;IACH,MAFU,IAAI,CAEkC;IAEhD;;;OAGG;IACH,SAFU,OAAO,CAEK;IAEtB;;;;;;;;OAQG;IACH,mBAA+C;IAE/C;;;OAGG;IACH,YAFU,UAAU,CAEwC;IAE5D;;;;;OAKG;IACH,gBAAyC;IAEzC;;;;;OAKG;IACH,wBAAmD;IAEnD;;;OAGG;IACH,IAFU,UAAU,CAEgC;IAEpD;;;OAGG;IACH,QAFU,MAAM,CAMf;IAED;;;OAGG;IACH,KAFU,GAAG,CAEc;IAE3B;;;OAGG;IACH,WAFU,SAAS,CAEoB;IAEvC;;;OAGG;IACH,OAFU,KAAK,CAEsC;IAErD;;;OAGG;IACH,MAFU,IAAI,CAEQ;IAEtB;;;OAGG;IACH,YAFU,UAAU,CAE+C;IAEnE;;;OAGG;IACH,KAFU,8BAA8B,CAE1B;IAEd;;;;OAIG;IACH,MAFU,IAAI,CAEuC;IAErD;;;OAGG;IACH,SAFU,cAAc,CAEkB;IAE1C;;;;OAIG;IACH,OAFW,KAAK,CAEwB;IAExC;;;OAGG;IACH,WA0BC;IAED;;;OAGG;IACH,QAFU,MAAM,CAEiB;IAEjC;;;;OAIG;IACH,QAHU,OAAO,CAGiB;IAElC;;;OAGG;IACH,SAFU,OAAO,CAEkB;CAEtC;sDAhQqD,4BAA4B;mCAC/C,wBAAwB;4BAZ/B,0BAA0B;sBAhBhC,oBAAoB;uBAwBnB,qBAAqB;wBAzBpB,sBAAsB;qBAGzB,mBAAmB;wBAChB,sBAAsB;2BAsBnB,yBAAyB;2BArBzB,yBAAyB;uBAC7B,qBAAqB;oBAExB,kBAAkB;mCAGH,wBAAwB;sBAFrC,oBAAoB;qBACrB,mBAAmB;2BAHb,yBAAyB;+CASL,qCAAqC;qBAF/D,mBAAmB;+BACT,sBAAsB;sBAF/B,oBAAoB;uBADnB,qBAAqB;wBAbpB,sBAAsB;wBAGtB,sBAAsB"} -------------------------------------------------------------------------------- /types/core/engine/context.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../lib/core/engine/context.js"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;GAGG;AAEH;;;;GAIG;AACH;IACE,2HA0DC;IAjDC;;;OAGG;IACH,aAAsB;IAEtB;;;OAGG;IACH,YAAoB;IAEpB;;OAEG;IACH,KAFU,MAAM,CAEF;IAEd;;;OAGG;IACH,OAFU,MAAM,CAEE;IAElB;;;OAGG;IACH,gBAFU,OAAO,iCAAiC,EAAE,cAAc,CAE9B;IAEpC;;OAEG;IACH,cAAkB;IAElB;;;;OAIG;IACH,UAFU;QAAC,SAAS,sCAAiB;QAAC,MAAM,yCAAmB;KAAC,CAW/D;CAEJ;;;;;WA9Ea,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;;;;aACtB,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;;;;UACtB,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;;;;UACtB,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;;;;WACtB,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;;;;cACtB,CAAS,IAAM,EAAN,MAAM,KAAG,IAAI;;6BAIvB,cAAc,oBAAoB,CAAC;gCACnC,OAAO,oBAAoB,EAAE,SAAS"} -------------------------------------------------------------------------------- /types/firefox/geckoProfiler.d.ts: -------------------------------------------------------------------------------- 1 | export class GeckoProfiler { 2 | constructor(runner: any, storageManager: any, options: any); 3 | runner: any; 4 | storageManager: any; 5 | firefoxConfig: any; 6 | options: any; 7 | start(): Promise; 8 | stop(index: any, url: any, result: any): Promise; 9 | addMetaData(): void; 10 | } 11 | //# sourceMappingURL=geckoProfiler.d.ts.map -------------------------------------------------------------------------------- /types/firefox/geckoProfiler.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"geckoProfiler.d.ts","sourceRoot":"","sources":["../../lib/firefox/geckoProfiler.js"],"names":[],"mappings":"AA8FA;IACE,4DAKC;IAJC,YAAoB;IACpB,oBAAoC;IACpC,mBAAoC;IACpC,aAAsB;IAGxB,sBA2FC;IAED,uDAkGC;IAED,oBAAgB;CACjB"} -------------------------------------------------------------------------------- /types/firefox/perfStats.d.ts: -------------------------------------------------------------------------------- 1 | export class PerfStats { 2 | constructor(runner: any); 3 | runner: any; 4 | start(featureMask: any): Promise; 5 | stop(): Promise; 6 | collect(): Promise<{}>; 7 | } 8 | //# sourceMappingURL=perfStats.d.ts.map -------------------------------------------------------------------------------- /types/firefox/perfStats.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"perfStats.d.ts","sourceRoot":"","sources":["../../lib/firefox/perfStats.js"],"names":[],"mappings":"AAGA;IACE,yBAEC;IADC,YAAoB;IAGtB,sCAIC;IAED,qBAIC;IAED,uBAuCC;CACF"} -------------------------------------------------------------------------------- /types/firefox/settings/geckoProfilerDefaults.d.ts: -------------------------------------------------------------------------------- 1 | export namespace geckoProfilerDefaults { 2 | let features: string; 3 | let threads: string; 4 | let desktopSamplingInterval: number; 5 | let androidSamplingInterval: number; 6 | let bufferSize: number; 7 | } 8 | //# sourceMappingURL=geckoProfilerDefaults.d.ts.map -------------------------------------------------------------------------------- /types/firefox/settings/geckoProfilerDefaults.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"geckoProfilerDefaults.d.ts","sourceRoot":"","sources":["../../../lib/firefox/settings/geckoProfilerDefaults.js"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /types/scripting.d.ts: -------------------------------------------------------------------------------- 1 | export { Context as BrowsertimeContext } from './core/engine/context'; 2 | export { Commands as BrowsertimeCommands } from './core/engine/commands'; -------------------------------------------------------------------------------- /types/support/engineUtils.d.ts: -------------------------------------------------------------------------------- 1 | export function loadPrePostScripts(scripts: any, options: any): Promise; 2 | export function loadPageCompleteScript(script: any): Promise; 3 | export function loadScript(script: any, options: any, throwError: any): Promise; 4 | export function timestamp(): string; 5 | //# sourceMappingURL=engineUtils.d.ts.map -------------------------------------------------------------------------------- /types/support/engineUtils.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"engineUtils.d.ts","sourceRoot":"","sources":["../../lib/support/engineUtils.js"],"names":[],"mappings":"AAkFA,+EAOC;AAED,kEAKC;AAED,qFAKC;AACD,oCAEC"} -------------------------------------------------------------------------------- /types/support/errors.d.ts: -------------------------------------------------------------------------------- 1 | export class BrowsertimeError extends Error { 2 | constructor(message: any, extra: any); 3 | extra: any; 4 | } 5 | export class BrowserError extends BrowsertimeError { 6 | } 7 | export class UrlLoadError extends BrowsertimeError { 8 | constructor(message: any, url: any, extra: any); 9 | url: any; 10 | } 11 | export class TimeoutError extends BrowsertimeError { 12 | constructor(message: any, url: any, extra: any); 13 | url: any; 14 | } 15 | //# sourceMappingURL=errors.d.ts.map -------------------------------------------------------------------------------- /types/support/errors.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../lib/support/errors.js"],"names":[],"mappings":"AAAA;IACE,sCAIC;IAFC,WAAwB;CAG3B;AAED;CAIC;AAED;IACE,gDAGC;IADC,SAAc;CAEjB;AAED;IACE,gDAGC;IADC,SAAc;CAEjB"} -------------------------------------------------------------------------------- /types/support/fileUtil.d.ts: -------------------------------------------------------------------------------- 1 | export function rename(old: any, newName: any): Promise; 2 | export function copyFileSync(source: any, destination: any): void; 3 | export function removeFileSync(fileName: any): Promise; 4 | export function copyFile(source: any, destination: any): Promise; 5 | export function removeFile(fileName: any): Promise; 6 | export function readFile(fileName: any): Promise; 7 | export function removeDirAndFiles(dirName: any): Promise; 8 | export function removeByType(dir: any, type: any): Promise; 9 | export function findFiles(dir: any, filter: any): Promise; 10 | export function findUpSync(filenames: any, startDir?: string): string; 11 | //# sourceMappingURL=fileUtil.d.ts.map -------------------------------------------------------------------------------- /types/support/fileUtil.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"fileUtil.d.ts","sourceRoot":"","sources":["../../lib/support/fileUtil.js"],"names":[],"mappings":"AAyBA,8DAEC;AAED,kEAEC;AAED,6DAEC;AAED,uEAEC;AACD,yDAEC;AACD,yDAEC;AACD,+DAqBC;AACD,iEAUC;AACD,oEAMC;AAED,sEAcC"} -------------------------------------------------------------------------------- /types/support/filters.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Filters to use with Array.prototype.filter, e.g. ['/a/path', '/another/path'].filter(onlyFiles) 3 | */ 4 | export function onlyWithExtension(extension: any): (filepath: any) => boolean; 5 | export function onlyFiles(filepath: any): Promise; 6 | export function onlyDirectories(filepath: any): Promise; 7 | //# sourceMappingURL=filters.d.ts.map -------------------------------------------------------------------------------- /types/support/filters.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../lib/support/filters.js"],"names":[],"mappings":"AAKA;;GAEG;AACH,8EAEC;AACD,2DAGC;AACD,iEAGC"} -------------------------------------------------------------------------------- /types/support/getViewPort.d.ts: -------------------------------------------------------------------------------- 1 | export function getViewPort(options: any): any; 2 | //# sourceMappingURL=getViewPort.d.ts.map -------------------------------------------------------------------------------- /types/support/getViewPort.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getViewPort.d.ts","sourceRoot":"","sources":["../../lib/support/getViewPort.js"],"names":[],"mappings":"AAqCA,+CAsCC"} -------------------------------------------------------------------------------- /types/support/pathToFolder.d.ts: -------------------------------------------------------------------------------- 1 | export function pathToFolder(url: any, options: any): string; 2 | //# sourceMappingURL=pathToFolder.d.ts.map -------------------------------------------------------------------------------- /types/support/pathToFolder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"pathToFolder.d.ts","sourceRoot":"","sources":["../../lib/support/pathToFolder.js"],"names":[],"mappings":"AAWA,6DAwDC"} -------------------------------------------------------------------------------- /types/support/storageManager.d.ts: -------------------------------------------------------------------------------- 1 | export class StorageManager { 2 | constructor(url: any, { resultDir, prettyPrint }?: { 3 | prettyPrint?: boolean; 4 | }); 5 | baseDir: string; 6 | jsonIndentation: number; 7 | createDataDir(): Promise; 8 | createSubDataDir(...name: any[]): Promise; 9 | rm(filename: any): Promise; 10 | writeData(filename: any, data: any, subdir: any): Promise; 11 | writeJson(filename: any, json: any, shouldGzip: any): Promise; 12 | readData(filename: any, subdir: any): Promise; 13 | gzip(inputFile: any, outputFile: any, removeInput: any): Promise; 14 | get directory(): string; 15 | } 16 | //# sourceMappingURL=storageManager.d.ts.map -------------------------------------------------------------------------------- /types/support/storageManager.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"storageManager.d.ts","sourceRoot":"","sources":["../../lib/support/storageManager.js"],"names":[],"mappings":"AAmDA;IACE;;OAKC;IAJC,gBAE6D;IAC7D,wBAA0C;IAG5C,iCAGC;IAED,kDAIC;IAED,iCAEC;IAED,kEASC;IAED,sEAQC;IAED,+DAcC;IAED,sEAmBC;IAED,wBAEC;CACF"} -------------------------------------------------------------------------------- /types/support/tcpdump.d.ts: -------------------------------------------------------------------------------- 1 | export class TCPDump { 2 | constructor(directory: any, options: any); 3 | baseDir: any; 4 | options: any; 5 | start(iteration: any): Promise; 6 | tcpdumpProcess: import("execa").ResultPromise<{}>; 7 | stop(): Promise; 8 | mv(url: any, iteration: any): Promise; 9 | } 10 | //# sourceMappingURL=tcpdump.d.ts.map -------------------------------------------------------------------------------- /types/support/tcpdump.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"tcpdump.d.ts","sourceRoot":"","sources":["../../lib/support/tcpdump.js"],"names":[],"mappings":"AAKA;IACE,0CAGC;IAFC,aAAwB;IACxB,aAAsB;IAExB,qCAsBC;IADC,kDAA+C;IAEjD,sBAKC;IAED,4CAQC;CACF"} -------------------------------------------------------------------------------- /types/support/usbPower.d.ts: -------------------------------------------------------------------------------- 1 | export function loadUsbPowerProfiler(): Promise; 2 | //# sourceMappingURL=usbPower.d.ts.map -------------------------------------------------------------------------------- /types/support/usbPower.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"usbPower.d.ts","sourceRoot":"","sources":["../../lib/support/usbPower.js"],"names":[],"mappings":"AAAA,qDAUC"} -------------------------------------------------------------------------------- /types/support/userTiming.d.ts: -------------------------------------------------------------------------------- 1 | export function filterAllowlisted(userTimings: any, allowlistRegex: any): void; 2 | export function filterBlocklisted(userTimings: any, blocklistRegex: any): void; 3 | //# sourceMappingURL=userTiming.d.ts.map -------------------------------------------------------------------------------- /types/support/userTiming.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"userTiming.d.ts","sourceRoot":"","sources":["../../lib/support/userTiming.js"],"names":[],"mappings":"AAAA,+EAMC;AAED,+EAQC"} -------------------------------------------------------------------------------- /types/support/util.d.ts: -------------------------------------------------------------------------------- 1 | export function formatMetric(name: any, metric: any, multiple: any, inMs: any, extras: any): string; 2 | export function logResultLogLine(results: any): void; 3 | export function toArray(arrayLike: any): any[]; 4 | export function jsonifyVisualProgress(visualProgress: any): any; 5 | export function jsonifyKeyColorFrames(keyColorFrames: any): any; 6 | export function adjustVisualProgressTimestamps(visualProgress: any, profilerStartTime: any, recordingStartTime: any): any; 7 | export function localTime(): string; 8 | export function pick(obj: any, keys: any): {}; 9 | export function isEmpty(value: any): boolean; 10 | export function setProperty(object: any, path: any, value: any): void; 11 | /** 12 | * A replacement for lodash.get(object, path, [defaultValue]). 13 | * 14 | */ 15 | export function getProperty(object: any, path: any, defaultValue: any): any; 16 | //# sourceMappingURL=util.d.ts.map -------------------------------------------------------------------------------- /types/support/util.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../lib/support/util.js"],"names":[],"mappings":"AAGA,oGAeC;AACD,qDA2LC;AACD,+CAQC;AACD,gEAeC;AACD,gEAsBC;AACD,0HAWC;AAED,oCAmBC;AAED,8CAWC;AAED,6CAsBC;AAcD,sEAsBC;AAED;;;GAGG;AACH,4EAuBC"} -------------------------------------------------------------------------------- /types/video/defaults.d.ts: -------------------------------------------------------------------------------- 1 | export const framerate: 30; 2 | export const crf: 23; 3 | export const xvfbDisplay: 99; 4 | export const addTimer: true; 5 | export const convert: true; 6 | export const threads: 0; 7 | //# sourceMappingURL=defaults.d.ts.map -------------------------------------------------------------------------------- /types/video/defaults.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../lib/video/defaults.js"],"names":[],"mappings":"AAAA,wBAAyB,EAAE,CAAC;AAC5B,kBAAmB,EAAE,CAAC;AACtB,0BAA2B,EAAE,CAAC;AAC9B,uBAAwB,IAAI,CAAC;AAC7B,sBAAuB,IAAI,CAAC;AAC5B,sBAAuB,CAAC,CAAC"} -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/addTextToVideo.d.ts: -------------------------------------------------------------------------------- 1 | export function addTextToVideo(inputFile: any, outputFile: any, videoMetrics: any, timingMetrics: any, options: any): Promise>; 2 | //# sourceMappingURL=addTextToVideo.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/addTextToVideo.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"addTextToVideo.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/finetune/addTextToVideo.js"],"names":[],"mappings":"AAgBA,0JAiCC"} -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/convertFps.d.ts: -------------------------------------------------------------------------------- 1 | export function convert(source: any, destination: any, framerate: any): Promise>; 2 | //# sourceMappingURL=convertFps.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/convertFps.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"convertFps.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/finetune/convertFps.js"],"names":[],"mappings":"AAIA,4GAWC"} -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/getFont.d.ts: -------------------------------------------------------------------------------- 1 | export function getFont(options: any): string; 2 | //# sourceMappingURL=getFont.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/getFont.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getFont.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/finetune/getFont.js"],"names":[],"mappings":"AACA,8CA6BC"} -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/getTimingMetrics.d.ts: -------------------------------------------------------------------------------- 1 | export function getTimingMetrics(videoMetrics: any, timingMetrics: any, options: any): string; 2 | //# sourceMappingURL=getTimingMetrics.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/getTimingMetrics.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getTimingMetrics.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/finetune/getTimingMetrics.js"],"names":[],"mappings":"AAgCA,8FAsDC"} -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/index.d.ts: -------------------------------------------------------------------------------- 1 | export function finetuneVideo(videoDir: any, videoPath: any, index: any, videoMetrics: any, timingMetrics: any, options: any): Promise; 2 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/finetune/index.js"],"names":[],"mappings":"AAmBA,6IAmEC"} -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/removeOrange.d.ts: -------------------------------------------------------------------------------- 1 | export function removeOrange(inputFile: any, outputFile: any, newStart: any, visualMetrics: any, options: any): Promise>; 2 | //# sourceMappingURL=removeOrange.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/finetune/removeOrange.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"removeOrange.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/finetune/removeOrange.js"],"names":[],"mappings":"AAIA,oJAkBC"} -------------------------------------------------------------------------------- /types/video/postprocessing/visualmetrics/extraMetrics.d.ts: -------------------------------------------------------------------------------- 1 | export function extraMetrics(metrics: any): { 2 | videoRecordingStart: any; 3 | visualMetrics: any; 4 | }; 5 | //# sourceMappingURL=extraMetrics.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/visualmetrics/extraMetrics.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"extraMetrics.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/visualmetrics/extraMetrics.js"],"names":[],"mappings":"AAAA;;;EA8CC"} -------------------------------------------------------------------------------- /types/video/postprocessing/visualmetrics/getVideoMetrics.d.ts: -------------------------------------------------------------------------------- 1 | export function getVideoMetrics(videoDir: any, filmstripDir: any, videoPath: any, index: any, visualElements: any, storageManager: any, pageNumber: any, visitedPageNumber: any, options: any): Promise<{ 2 | videoRecordingStart: any; 3 | visualMetrics: any; 4 | }>; 5 | //# sourceMappingURL=getVideoMetrics.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/visualmetrics/getVideoMetrics.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getVideoMetrics.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/visualmetrics/getVideoMetrics.js"],"names":[],"mappings":"AAMA;;;GA+CC"} -------------------------------------------------------------------------------- /types/video/postprocessing/visualmetrics/visualMetrics.d.ts: -------------------------------------------------------------------------------- 1 | export function checkDependencies(options: any): Promise>; 2 | export function run(videoPath: any, imageDirPath: any, elementsFile: any, videoDir: any, index: any, pageNumber: any, visitedPageNumber: any, options: any): Promise; 3 | //# sourceMappingURL=visualMetrics.d.ts.map -------------------------------------------------------------------------------- /types/video/postprocessing/visualmetrics/visualMetrics.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"visualMetrics.d.ts","sourceRoot":"","sources":["../../../../lib/video/postprocessing/visualmetrics/visualMetrics.js"],"names":[],"mappings":"AAsCA,qFAEC;AACD,0KAqIC"} -------------------------------------------------------------------------------- /types/video/screenRecording/android/recorder.d.ts: -------------------------------------------------------------------------------- 1 | export class AndroidRecorder { 2 | constructor(options: any); 3 | waitTime: any; 4 | framerate: any; 5 | options: any; 6 | start(): Promise; 7 | android: Android; 8 | stop(destination: any): Promise; 9 | } 10 | import { Android } from '../../../android/index.js'; 11 | //# sourceMappingURL=recorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/android/recorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/android/recorder.js"],"names":[],"mappings":"AAWA;IACE,0BAQC;IAPC,cAIC;IACD,eAAyE;IACzE,aAAsB;IAGxB,sBAGC;IAFC,iBAAwC;IAI1C,qCAgBC;CACF;wBAxCuB,2BAA2B"} -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/convert.d.ts: -------------------------------------------------------------------------------- 1 | export function convert(source: any, destination: any, crf: any, threads: any): Promise>; 2 | //# sourceMappingURL=convert.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/convert.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"convert.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/desktop/convert.js"],"names":[],"mappings":"AAIA,oHAwBC"} -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/desktopRecorder.d.ts: -------------------------------------------------------------------------------- 1 | export class DesktopRecorder { 2 | constructor(options: any); 3 | display: any; 4 | framerate: any; 5 | nice: any; 6 | crf: any; 7 | convert: any; 8 | threads: any; 9 | viewPort: any; 10 | taskset: any; 11 | origin: string; 12 | offset: { 13 | x: number; 14 | y: number; 15 | }; 16 | options: any; 17 | start(file: any): Promise<{ 18 | filePath: any; 19 | ffmpegProcess: import("execa").ResultPromise; 20 | }>; 21 | filePath: any; 22 | recording: Promise<{ 23 | filePath: any; 24 | ffmpegProcess: import("execa").ResultPromise; 25 | }>; 26 | stop(destination: any): Promise; 27 | } 28 | //# sourceMappingURL=desktopRecorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/desktopRecorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"desktopRecorder.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/desktop/desktopRecorder.js"],"names":[],"mappings":"AAmBA;IACE,0BAYC;IAXC,aAAsE;IACtE,eAA0E;IAC1E,UAAuD;IACvD,SAAwD;IACxD,aAAoE;IACpE,aAAoE;IACpE,cAAoC;IACpC,aAA0D;IAC1D,eAAmB;IACnB;;;MAA4B;IAC5B,aAAsB;IAGxB;;;OAiBC;IAhBC,cAAoB;IAEpB;;;OAWE;IAKJ,sCAoCC;CACF"} -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/ffmpegRecorder.d.ts: -------------------------------------------------------------------------------- 1 | export function start({ display, origin, size, filePath, offset, framerate, crf, nice, threads, taskset }: { 2 | display: any; 3 | origin: any; 4 | size: any; 5 | filePath: any; 6 | offset: any; 7 | framerate: any; 8 | crf: any; 9 | nice: any; 10 | threads: any; 11 | taskset: any; 12 | }): Promise<{ 13 | filePath: any; 14 | ffmpegProcess: import("execa").ResultPromise; 15 | }>; 16 | export function stop(recording: any): Promise; 17 | //# sourceMappingURL=ffmpegRecorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/ffmpegRecorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ffmpegRecorder.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/desktop/ffmpegRecorder.js"],"names":[],"mappings":"AAsIA;;;;;;;;;;;;;;GAiCC;AACD,mDAaC"} -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/osx/getSPDisplaysDataType.d.ts: -------------------------------------------------------------------------------- 1 | export function getSPDisplaysDataType(): Promise; 2 | //# sourceMappingURL=getSPDisplaysDataType.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/osx/getSPDisplaysDataType.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getSPDisplaysDataType.d.ts","sourceRoot":"","sources":["../../../../../lib/video/screenRecording/desktop/osx/getSPDisplaysDataType.js"],"names":[],"mappings":"AAEA,yDAKC"} -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/osx/getScreen.d.ts: -------------------------------------------------------------------------------- 1 | export function getScreenOnOSX(): Promise; 2 | //# sourceMappingURL=getScreen.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/desktop/osx/getScreen.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"getScreen.d.ts","sourceRoot":"","sources":["../../../../../lib/video/screenRecording/desktop/osx/getScreen.js"],"names":[],"mappings":"AAIA,kDA0BC"} -------------------------------------------------------------------------------- /types/video/screenRecording/firefox/firefoxWindowRecorder.d.ts: -------------------------------------------------------------------------------- 1 | export class FirefoxWindowRecorder { 2 | constructor(options: any, browser: any, baseDir: any); 3 | options: any; 4 | browser: any; 5 | baseDir: any; 6 | recordingStartTime: any; 7 | timeToFirstFrame: number; 8 | start(): Promise; 9 | android: Android; 10 | stop(destination: any): Promise; 11 | } 12 | import { Android } from '../../../android/index.js'; 13 | //# sourceMappingURL=firefoxWindowRecorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/firefox/firefoxWindowRecorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"firefoxWindowRecorder.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/firefox/firefoxWindowRecorder.js"],"names":[],"mappings":"AAuJA;IACE,sDAMC;IALC,aAAsB;IACtB,aAAsB;IACtB,aAAsB;IACtB,wBAAmC;IACnC,yBAAiC;IAGnC,sBAYC;IARG,iBAAwC;IAU5C,sCAgCC;CACF;wBAnM4C,2BAA2B"} -------------------------------------------------------------------------------- /types/video/screenRecording/ios/convertToMp4.d.ts: -------------------------------------------------------------------------------- 1 | export function convert(source: any, destination: any): Promise>; 2 | //# sourceMappingURL=convertToMp4.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/ios/convertToMp4.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"convertToMp4.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/ios/convertToMp4.js"],"names":[],"mappings":"AAIA,4FAiBC"} -------------------------------------------------------------------------------- /types/video/screenRecording/ios/iosRecorder.d.ts: -------------------------------------------------------------------------------- 1 | export class IOSRecorder { 2 | static activate(): Promise; 5 | constructor(options: any, baseDir: any); 6 | options: any; 7 | uuid: any; 8 | tmpVideo: string; 9 | tmpSound: string; 10 | start(): Promise; 11 | qvhProcessProcess: import("execa").ResultPromise<{ 12 | shell: true; 13 | }>; 14 | stop(destination: any): Promise>; 17 | } 18 | //# sourceMappingURL=iosRecorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/ios/iosRecorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"iosRecorder.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/ios/iosRecorder.js"],"names":[],"mappings":"AAcA;IAQE;;OAMC;IAbD,wCAKC;IAJC,aAAsB;IACtB,UAAqC;IACrC,iBAA8C;IAC9C,iBAA6C;IAW/C,uBAqBC;IAlBC;;OAQC;IAYH;;QAaC;CACF"} -------------------------------------------------------------------------------- /types/video/screenRecording/iosSimulator/convertToMp4.d.ts: -------------------------------------------------------------------------------- 1 | export function convert(source: any, destination: any): Promise>; 2 | //# sourceMappingURL=convertToMp4.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/iosSimulator/convertToMp4.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"convertToMp4.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/iosSimulator/convertToMp4.js"],"names":[],"mappings":"AAIA,4FAiBC"} -------------------------------------------------------------------------------- /types/video/screenRecording/iosSimulator/recorder.d.ts: -------------------------------------------------------------------------------- 1 | export class IOSSimulatorRecorder { 2 | constructor(options: any, baseDir: any); 3 | options: any; 4 | tmpVideo: string; 5 | start(): Promise; 6 | xcrunProcess: import("execa").ResultPromise<{ 7 | shell: true; 8 | }>; 9 | stop(destination: any): Promise>; 12 | } 13 | //# sourceMappingURL=recorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/iosSimulator/recorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../../lib/video/screenRecording/iosSimulator/recorder.js"],"names":[],"mappings":"AAUA;IACE,wCAGC;IAFC,aAAsB;IACtB,iBAA6C;IAG/C,uBAUC;IAPC;;OAMC;IAGH;;QAWC;CACF"} -------------------------------------------------------------------------------- /types/video/screenRecording/recorder.d.ts: -------------------------------------------------------------------------------- 1 | export function getRecorder(options: any, browser: any, baseDir: any): AndroidRecorder | DesktopRecorder | FirefoxWindowRecorder | IOSSimulatorRecorder | IOSRecorder; 2 | import { AndroidRecorder } from './android/recorder.js'; 3 | import { DesktopRecorder } from './desktop/desktopRecorder.js'; 4 | import { FirefoxWindowRecorder } from './firefox/firefoxWindowRecorder.js'; 5 | import { IOSSimulatorRecorder } from './iosSimulator/recorder.js'; 6 | import { IOSRecorder } from './ios/iosRecorder.js'; 7 | //# sourceMappingURL=recorder.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/recorder.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../../lib/video/screenRecording/recorder.js"],"names":[],"mappings":"AAOA,sKA0BC;gCAhC+B,uBAAuB;gCACvB,8BAA8B;sCACxB,oCAAoC;qCACrC,4BAA4B;4BACrC,sBAAsB"} -------------------------------------------------------------------------------- /types/video/screenRecording/setOrangeBackground.d.ts: -------------------------------------------------------------------------------- 1 | export function setOrangeBackground(driver: any): Promise; 2 | //# sourceMappingURL=setOrangeBackground.d.ts.map -------------------------------------------------------------------------------- /types/video/screenRecording/setOrangeBackground.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"setOrangeBackground.d.ts","sourceRoot":"","sources":["../../../lib/video/screenRecording/setOrangeBackground.js"],"names":[],"mappings":"AAGA,+DAyBC"} -------------------------------------------------------------------------------- /types/video/video.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a new Video that handles everything with the video 3 | * @class 4 | */ 5 | export class Video { 6 | constructor(storageManager: any, options: any, browser: any); 7 | options: any; 8 | storageManager: any; 9 | tmpDir: any; 10 | recorder: import("./screenRecording/android/recorder.js").AndroidRecorder | import("./screenRecording/desktop/desktopRecorder.js").DesktopRecorder | import("./screenRecording/firefox/firefoxWindowRecorder.js").FirefoxWindowRecorder | import("./screenRecording/iosSimulator/recorder.js").IOSSimulatorRecorder | import("./screenRecording/ios/iosRecorder.js").IOSRecorder; 11 | isRecording: boolean; 12 | setupDirs(index: any, url: any): Promise; 13 | index: any; 14 | videoDir: any; 15 | filmstripDir: any; 16 | /** 17 | * Start recoding a video. 18 | * @returns {Promise} Promise object that represents when the video started 19 | */ 20 | record(pageNumber: any, index: any, visitedPageNumber: any): Promise; 21 | pageNumber: any; 22 | visitedPageNumber: any; 23 | /** 24 | * Stop recording the video. 25 | * @returns {Promise} Promise object that represents when the video stopped 26 | */ 27 | stop(url: any): Promise; 28 | videoPath: string; 29 | cleanup(): Promise; 30 | /** 31 | Post process video: get visual metrics, finetune the video and remove it 32 | if you don't want it 33 | */ 34 | postProcessing(timingMetrics: any, visualElements: any): Promise<{ 35 | videoRecordingStart: any; 36 | visualMetrics: any; 37 | }>; 38 | getRecordingStartTime(): any; 39 | getTimeToFirstFrame(): any; 40 | } 41 | //# sourceMappingURL=video.d.ts.map -------------------------------------------------------------------------------- /types/video/video.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../lib/video/video.js"],"names":[],"mappings":"AAOA;;;GAGG;AACH;IACE,6DAMC;IALC,aAAsB;IACtB,oBAAoC;IACpC,YAAsC;IACtC,iXAA0D;IAC1D,qBAAwB;IAG1B,+CAeC;IAbC,WAAkB;IAElB,cAEC;IAMD,kBAEC;IAGH;;;OAGG;IACH,0EAUC;IATC,gBAA4B;IAE5B,uBAA0C;IAS5C;;;OAGG;IACH,6BAcC;IATG,kBAKC;IAML,yBAaC;IAED;;;OAGG;IACH;;;OA0BC;IAED,6BAEC;IAED,2BAEC;CACF"} -------------------------------------------------------------------------------- /visualmetrics/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_000000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_000000.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_000920.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_000920.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001000.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001080.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001080.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001200.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001240.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001280.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001360.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001400.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_001520.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_001520.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_002040.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_002040.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_002600.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_002600.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_003160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_003160.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_003720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_003720.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_004280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_004280.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_004880.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_004880.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_005440.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_005440.png -------------------------------------------------------------------------------- /visualmetrics/test_data/ms_006000.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sitespeedio/browsertime/c8fe4c46e374a08a5b43e7bc0ff48f749088264e/visualmetrics/test_data/ms_006000.png -------------------------------------------------------------------------------- /visualmetrics/test_visualmetrics.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | 4 | from browsertime.visualmetrics import ( 5 | calculate_contentful_speed_index, 6 | calculate_perceptual_speed_index, 7 | ) 8 | 9 | HERE = os.path.dirname(__file__) 10 | 11 | 12 | class TestVisualMetrics(unittest.TestCase): 13 | def setUp(self): 14 | self.directory = "test_data" 15 | images = os.listdir(os.path.join(HERE, self.directory)) 16 | 17 | def _p(image): 18 | p = {} 19 | p["time"] = int(image.split(".")[0].split("ms_")[-1]) 20 | return p 21 | 22 | progress = [_p(image) for image in images if image.startswith("ms_")] 23 | self.sorted_progress = sorted(progress, 24 | key = lambda image: image['time']) 25 | 26 | def test_calculate_contentful_speed_index(self): 27 | res = calculate_contentful_speed_index(self.sorted_progress, 28 | self.directory) 29 | self.assertEqual(res[0], 1188) 30 | 31 | def test_calculate_perceptual_speed_index(self): 32 | res = calculate_perceptual_speed_index(self.sorted_progress, 33 | self.directory) 34 | self.assertEqual(res[0], 946) 35 | --------------------------------------------------------------------------------