├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ ├── _backstop-ci-runner.yml
│ ├── backstop-publish.yml
│ ├── backstop-reference-test.yml
│ ├── backstop-sanity-docker.yml
│ ├── docker-sanity-test.yml
│ ├── dockerhub-build-push.yml
│ ├── npm-push.yml
│ └── test-build-pub-npm-dockerhub.yml
├── .gitignore
├── .gitlab-ci.yml
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── assets
├── BackstopJS.pdf
├── BackstopJS.png
├── BackstopJS.svg
├── BackstopJS3.svg
├── approve_feature_hilite_sm.png
├── backstop_banner.jpg
├── backstop_banner.png
├── backstopjs_new_ui_.png
├── cli-report.png
├── duskBg.png
├── duskBg1.png
├── github-icon.png
├── lemurButt.png
├── lemurFace.pdf
├── lemurFace.png
├── lemurFace2.png
├── memes
│ ├── do_not_razz_the_lemur.png
│ ├── im-in-ur-webapps-checkin-ur-scr33ns-1.jpg
│ ├── im-in-ur-webapps-checking-ur-screens.jpg
│ ├── lemur_template_square.png
│ └── lemur_template_wide.png
├── osRenderDifference.png
├── scoochAndTomster.png
└── styles.css
├── capture
├── backstopTools.js
├── config.default.json
├── engine_scripts
│ ├── cookies.json
│ ├── imageStub.jpg
│ ├── playwright
│ │ ├── clickAndHoverHelper.js
│ │ ├── interceptImages.js
│ │ ├── loadCookies.js
│ │ ├── onBefore.js
│ │ ├── onReady.js
│ │ └── overrideCSS.js
│ └── puppet
│ │ ├── clickAndHoverHelper.js
│ │ ├── ignoreCSP.js
│ │ ├── interceptImages.js
│ │ ├── loadCookies.js
│ │ ├── onBefore.js
│ │ ├── onReady.js
│ │ └── overrideCSS.js
└── resources
│ ├── hiddenSelector_noun_63405.png
│ ├── notFound.png
│ ├── notVisible.png
│ ├── selectorNotFound_noun_164558_cc.png
│ ├── unexpectedError.png
│ └── unexpectedErrorSm.png
├── changelog.md
├── cli
├── index.js
└── usage.js
├── compare
├── README.md
├── output
│ ├── a96f14595379b7c348d66e115ec65a93.png
│ ├── assets
│ │ └── fonts
│ │ │ ├── Lato-Bold.ttf
│ │ │ ├── Lato-Regular.ttf
│ │ │ ├── lato-bold-webfont.woff
│ │ │ ├── lato-bold-webfont.woff2
│ │ │ ├── lato-regular-webfont.woff
│ │ │ └── lato-regular-webfont.woff2
│ ├── b815e28b1e230cff6e9d7b749edcd562.png
│ ├── diff.js
│ ├── diverged.js
│ ├── divergedWorker.js
│ ├── index.html
│ ├── index_bundle.js
│ └── index_bundle.js.LICENSE.txt
├── src
│ ├── .eslintrc
│ ├── actions
│ │ └── index.js
│ ├── assets
│ │ ├── icons
│ │ │ ├── close.png
│ │ │ ├── iconDown.png
│ │ │ ├── search.png
│ │ │ ├── settings.png
│ │ │ └── settings_2.png
│ │ └── images
│ │ │ └── logo.png
│ ├── components
│ │ ├── App.js
│ │ ├── atoms
│ │ │ ├── ButtonFilter.js
│ │ │ ├── ButtonSettings.js
│ │ │ ├── DiffDetails.js
│ │ │ ├── ErrorMessages.js
│ │ │ ├── IdContainer.js
│ │ │ ├── ImagePreview.js
│ │ │ ├── ImageScrubber.js
│ │ │ ├── InputTextSearch.js
│ │ │ ├── LogDetails.js
│ │ │ ├── Logo.js
│ │ │ ├── NavButtons.js
│ │ │ ├── SettingOption.js
│ │ │ ├── SuiteName.js
│ │ │ ├── TextDetails.js
│ │ │ └── UrlDetails.js
│ │ ├── ecosystems
│ │ │ ├── Header.js
│ │ │ ├── List.js
│ │ │ ├── LogModal.js
│ │ │ └── ScrubberModal.js
│ │ ├── molecules
│ │ │ ├── ApproveButton.js
│ │ │ ├── FiltersSwitch.js
│ │ │ ├── ScrubberButton.js
│ │ │ ├── SettingsContainer.js
│ │ │ ├── SettingsPopup.js
│ │ │ ├── TestImages.js
│ │ │ └── TextSearch.js
│ │ └── organisms
│ │ │ ├── TestCard.js
│ │ │ ├── Toolbar.js
│ │ │ └── Topbar.js
│ ├── index.js
│ ├── reducers
│ │ ├── index.js
│ │ ├── layoutSettings.js
│ │ ├── logs.js
│ │ ├── scrubber.js
│ │ ├── suiteInfo.js
│ │ └── tests.js
│ ├── store.js
│ └── styles
│ │ └── index.js
└── webpack.config.js
├── core
├── command
│ ├── approve.js
│ ├── index.js
│ ├── init.js
│ ├── openReport.js
│ ├── reference.js
│ ├── remote.js
│ ├── report.js
│ ├── stop.js
│ ├── test.js
│ └── version.js
├── runner.js
└── util
│ ├── BackstopException.js
│ ├── Reporter.js
│ ├── allSettled.js
│ ├── compare
│ ├── compare-hash.js
│ ├── compare-resemble.js
│ ├── compare.js
│ ├── index.js
│ ├── store-failed-diff-stub.js
│ └── store-failed-diff.js
│ ├── createBitmaps.js
│ ├── engineErrors.js
│ ├── engineTools.js
│ ├── ensureDirectoryPath.js
│ ├── extendConfig.js
│ ├── findExecutable.js
│ ├── fs.js
│ ├── getFreePorts.js
│ ├── getRemotePort.js
│ ├── isWin.js
│ ├── logger.js
│ ├── makeConfig.js
│ ├── makeSpaces.js
│ ├── promisify.js
│ ├── remote.js
│ ├── runDocker.js
│ ├── runPlaywright.js
│ ├── runPuppet.js
│ └── streamToPromise.js
├── docker
├── Dockerfile
├── README.md
├── burn-docker-builder.sh
├── docker-hub-config.pdf
├── docker-repo-config.png
├── hooks
│ ├── build
│ └── post_push
└── xvfb-run
├── examples
├── Jenkins
│ ├── Attachments
│ │ ├── Jenkins_AgentSecret.png
│ │ ├── Jenkins_BackstopJob.png
│ │ ├── Jenkins_ConfigNewNode.png
│ │ ├── Jenkins_General.png
│ │ ├── Jenkins_NewNodeName.png
│ │ ├── Jenkins_PostBuildActions_AvoidJUnitReportIssue.png
│ │ ├── Jenkins_PostBuildActions_DeleteContainer.png
│ │ ├── Jenkins_PostBuildActions_PublishHTML.png
│ │ ├── Jenkins_PostBuildActions_PublishJUnit.png
│ │ ├── Jenkins_Report.png
│ │ ├── Jenkins_SlaveReady.png
│ │ └── Jenkins_SourceControlManagement.png
│ ├── README.md
│ └── Sample
│ │ ├── backstop.json
│ │ └── backstop_data
│ │ ├── bitmaps_reference
│ │ ├── demo_demo_0_document_0_PC.png
│ │ └── demo_demo_0_document_1_iPhone66s78.png
│ │ └── engine_scripts
│ │ └── puppet
│ │ ├── clickAndHoverHelper.js
│ │ ├── ignoreCSP.js
│ │ ├── interceptImages.js
│ │ ├── loadCookies.js
│ │ ├── onBefore.js
│ │ └── onReady.js
├── angularAppWithCssTransitions
│ ├── angular.min.js
│ ├── backstopConfig_1.json
│ ├── backstopConfig_2.json
│ ├── bootstrap.min.css
│ └── index.html
├── featureTests
│ ├── dist
│ │ ├── css
│ │ │ ├── bootstrap-theme.css
│ │ │ ├── bootstrap-theme.css.map
│ │ │ ├── bootstrap-theme.min.css
│ │ │ ├── bootstrap.css
│ │ │ ├── bootstrap.css.map
│ │ │ └── bootstrap.min.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ └── glyphicons-halflings-regular.woff
│ │ └── js
│ │ │ ├── bootstrap.js
│ │ │ ├── bootstrap.min.js
│ │ │ └── npm.js
│ ├── index.html
│ └── readme.md
├── jsBasedConfig
│ ├── backstopConfig.js
│ └── readme.md
├── myCoolProject
│ ├── dist
│ │ ├── css
│ │ │ ├── bootstrap-theme.css
│ │ │ ├── bootstrap-theme.css.map
│ │ │ ├── bootstrap-theme.min.css
│ │ │ ├── bootstrap.css
│ │ │ ├── bootstrap.css.map
│ │ │ └── bootstrap.min.css
│ │ ├── fonts
│ │ │ ├── glyphicons-halflings-regular.eot
│ │ │ ├── glyphicons-halflings-regular.svg
│ │ │ ├── glyphicons-halflings-regular.ttf
│ │ │ └── glyphicons-halflings-regular.woff
│ │ └── js
│ │ │ ├── bootstrap.js
│ │ │ ├── bootstrap.min.js
│ │ │ └── npm.js
│ ├── index.html
│ └── readme.md
├── nodeIntegration
│ ├── backstop.config.js
│ ├── backstop.js
│ ├── package.json
│ ├── public
│ │ ├── first-project
│ │ │ ├── about-us.html
│ │ │ ├── homepage.html
│ │ │ └── ignore-me.html
│ │ ├── second-project
│ │ │ ├── contact-us.html
│ │ │ ├── dummy.json
│ │ │ ├── ignore-me.html
│ │ │ └── index.html
│ │ └── third-project
│ │ │ ├── ignore-me.html
│ │ │ └── terms-of-use.html
│ ├── readme.md
│ └── server.js
├── responsiveDemo
│ ├── assets
│ │ └── js
│ │ │ └── vendor
│ │ │ ├── anchor.min.js
│ │ │ ├── clipboard.min.js
│ │ │ ├── holder.min.js
│ │ │ ├── jquery-slim.min.js
│ │ │ └── popper.min.js
│ ├── backstop.json
│ ├── backstop_data
│ │ ├── bitmaps_reference
│ │ │ ├── backstop_default_BackstopJS_Homepage_0_document_0_phone.png
│ │ │ ├── backstop_default_BackstopJS_Homepage_0_document_1_tablet.png
│ │ │ ├── responsiveDemoPage_comparePage_0_document_0_phone.png
│ │ │ └── responsiveDemoPage_comparePage_0_document_1_tablet.png
│ │ └── engine_scripts
│ │ │ ├── cookies.json
│ │ │ ├── imageStub.jpg
│ │ │ └── puppet
│ │ │ ├── clickAndHoverHelper.js
│ │ │ ├── ignoreCSP.js
│ │ │ ├── interceptImages.js
│ │ │ ├── loadCookies.js
│ │ │ ├── onBefore.js
│ │ │ └── onReady.js
│ ├── dist
│ │ ├── css
│ │ │ ├── bootstrap.min.css
│ │ │ └── bootstrap.min.css.map
│ │ └── js
│ │ │ ├── bootstrap.min.js
│ │ │ └── bootstrap.min.js.map
│ ├── do_not_razz_the_lemur.png
│ ├── favicon.ico
│ ├── index.html
│ ├── package-lock.json
│ ├── package.json
│ ├── readme.md
│ └── responsiveDemo.css
└── simpleReactApp
│ ├── assets
│ └── css
│ │ └── styles.css
│ ├── backstop.json
│ ├── backstop_data
│ └── bitmaps_reference
│ │ ├── My Homepage_0_main_0_phone.png
│ │ ├── My Homepage_0_main_1_tablet_v.png
│ │ └── My Homepage_0_main_2_tablet_h.png
│ ├── compiled.js
│ ├── components
│ ├── App.js
│ ├── CurrentLocation.js
│ ├── LocationItem.js
│ ├── LocationList.js
│ ├── Map.js
│ └── Search.js
│ ├── index.html
│ ├── main.js
│ ├── package.json
│ └── readme.md
├── index.html
├── old_splash_page_v2.0
├── apple-touch-icon-precomposed.png
├── css
│ ├── bootstrap-theme.css
│ ├── bootstrap-theme.css.map
│ ├── bootstrap-theme.min.css
│ ├── bootstrap.css
│ ├── bootstrap.css.map
│ ├── bootstrap.min.css
│ └── main.css
├── favicon.ico
├── fonts
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.svg
│ ├── glyphicons-halflings-regular.ttf
│ └── glyphicons-halflings-regular.woff
├── img
│ ├── CLI_report.png
│ ├── backgrounds
│ │ ├── cream_dust
│ │ │ ├── cream_dust.png
│ │ │ ├── cream_dust_@2X.png
│ │ │ └── readme.txt
│ │ └── subtlenet2
│ │ │ ├── readme.txt
│ │ │ ├── subtlenet2.png
│ │ │ └── subtlenet2_@2X.png
│ ├── browserReport.png
│ ├── one.png
│ ├── one@2x.png
│ ├── three.png
│ ├── three@2x.png
│ ├── two.png
│ └── two@2x.png
├── index.html
└── js
│ ├── main.js
│ └── vendor
│ ├── bootstrap.js
│ ├── bootstrap.min.js
│ ├── html5-3.6-respond-1.1.0.min.js
│ └── jquery-1.11.1.min.js
├── package-lock.json
├── package.json
├── remote
└── index.js
└── test
├── .eslintrc
├── cli
├── index_spec.js
└── usage_spec.js
├── configs
├── backstop.json
├── backstop_alt.js
├── backstop_data
│ ├── bitmaps_reference
│ │ ├── backstop_default_BackstopJS_Homepage_0_document_0_phone.png
│ │ ├── backstop_default_BackstopJS_Homepage_0_document_1_tablet.png
│ │ ├── backstop_playwright_BackstopJS_Homepage_0_document_0_phone.png
│ │ ├── backstop_playwright_BackstopJS_Homepage_0_document_1_tablet.png
│ │ ├── puppet_backstop_features_Simple_0_document_0_phone.png
│ │ ├── puppet_backstop_features_Simple_0_document_1_tablet.png
│ │ ├── puppet_backstop_features_click_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_click_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_cookies_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_cookies_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_delay_0_getItBlocknth-child3_0_phone.png
│ │ ├── puppet_backstop_features_delay_0_getItBlocknth-child3_1_tablet.png
│ │ ├── puppet_backstop_features_expanded_0_getItBlock_0_phone.png
│ │ ├── puppet_backstop_features_expanded_0_getItBlock_1_tablet.png
│ │ ├── puppet_backstop_features_expanded_1_getItBlock__n1_0_phone.png
│ │ ├── puppet_backstop_features_expanded_1_getItBlock__n1_1_tablet.png
│ │ ├── puppet_backstop_features_expanded_2_getItBlock__n2_0_phone.png
│ │ ├── puppet_backstop_features_expanded_2_getItBlock__n2_1_tablet.png
│ │ ├── puppet_backstop_features_expanded_3_getItBlock__n3_0_phone.png
│ │ ├── puppet_backstop_features_expanded_3_getItBlock__n3_1_tablet.png
│ │ ├── puppet_backstop_features_expect_0_getItBlock_0_phone.png
│ │ ├── puppet_backstop_features_expect_0_getItBlock_1_tablet.png
│ │ ├── puppet_backstop_features_expect_1_getItBlock__n1_0_phone.png
│ │ ├── puppet_backstop_features_expect_1_getItBlock__n1_1_tablet.png
│ │ ├── puppet_backstop_features_expect_2_getItBlock__n2_0_phone.png
│ │ ├── puppet_backstop_features_expect_2_getItBlock__n2_1_tablet.png
│ │ ├── puppet_backstop_features_expect_3_getItBlock__n3_0_phone.png
│ │ ├── puppet_backstop_features_expect_3_getItBlock__n3_1_tablet.png
│ │ ├── puppet_backstop_features_hideSelectors_0_document_0_phone.png
│ │ ├── puppet_backstop_features_hideSelectors_0_document_1_tablet.png
│ │ ├── puppet_backstop_features_hover_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_hover_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_keyPressSelector_0_dividnavbar_0_Desktop.png
│ │ ├── puppet_backstop_features_magicSelectors_0_document_0_phone.png
│ │ ├── puppet_backstop_features_magicSelectors_0_document_1_tablet.png
│ │ ├── puppet_backstop_features_magicSelectors_1_viewport_0_phone.png
│ │ ├── puppet_backstop_features_magicSelectors_1_viewport_1_tablet.png
│ │ ├── puppet_backstop_features_noDelay_0_getItBlocknth-child3_0_phone.png
│ │ ├── puppet_backstop_features_noDelay_0_getItBlocknth-child3_1_tablet.png
│ │ ├── puppet_backstop_features_notExpanded_0_getItBlock_0_phone.png
│ │ ├── puppet_backstop_features_notExpanded_0_getItBlock_1_tablet.png
│ │ ├── puppet_backstop_features_notFound_0_monkey_0_phone.png
│ │ ├── puppet_backstop_features_notFound_0_monkey_1_tablet.png
│ │ ├── puppet_backstop_features_notVisible_0_noShow_0_phone.png
│ │ ├── puppet_backstop_features_notVisible_0_noShow_1_tablet.png
│ │ ├── puppet_backstop_features_pkra_bug_test_0_pkratest_0_phone.png
│ │ ├── puppet_backstop_features_pkra_bug_test_0_pkratest_1_tablet.png
│ │ ├── puppet_backstop_features_pkra_bug_test_1_logoBlock_0_phone.png
│ │ ├── puppet_backstop_features_pkra_bug_test_1_logoBlock_1_tablet.png
│ │ ├── puppet_backstop_features_readyEventTimeout_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_readyEventTimeout_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_readyEvent_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_readyEvent_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_readySelectorTimeout_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_readySelectorTimeout_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_readySelector_0_moneyshot_0_phone.png
│ │ ├── puppet_backstop_features_readySelector_0_moneyshot_1_tablet.png
│ │ ├── puppet_backstop_features_removeSelectors_0_document_0_phone.png
│ │ ├── puppet_backstop_features_removeSelectors_0_document_1_tablet.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withEmptyViewports_0_document_0_phone.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withEmptyViewports_0_document_1_tablet.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_0_getItBlock_0_iPad-Pro.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_1_getItBlock__n1_0_iPad-Pro.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_2_getItBlock__n2_0_iPad-Pro.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_3_getItBlock__n3_0_iPad-Pro.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_0_Pixel-2.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_1_Pixel2-XL.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_2_iPhone-X.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_3_iPad-Pro.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports_0_document_0_Galaxy-S5.png
│ │ ├── puppet_backstop_features_scenarioSpecificViewports_1_viewport_0_Galaxy-S5.png
│ │ ├── puppet_backstop_features_scrollToSelector_0_lemurFace_0_phone.png
│ │ └── puppet_backstop_features_scrollToSelector_0_lemurFace_1_tablet.png
│ ├── cookies.json
│ └── engine_scripts
│ │ ├── cookies.json
│ │ ├── onBefore.js
│ │ ├── onReady.js
│ │ ├── playwright
│ │ ├── clickAndHoverHelper.js
│ │ ├── interceptImages.js
│ │ ├── loadCookies.js
│ │ ├── onBefore.js
│ │ ├── onReady.js
│ │ └── overrideCSS.js
│ │ └── puppet
│ │ ├── clickAndHoverHelper.js
│ │ ├── loadCookies.js
│ │ ├── onBefore.js
│ │ ├── onReady.js
│ │ └── overrideCSS.js
├── backstop_fail_cases.js
├── backstop_features.js
├── backstop_features_pw.js
├── dynamic_node_app.js
├── lemurs
│ ├── lemur-ring-tailed-lemur-primate-mammal.jpg
│ ├── pexels-photo-145940.jpg
│ ├── pexels-photo-146006.jpg
│ ├── pexels-photo-146039.jpg
│ └── pexels-photo-146049.jpg
├── multi_step node_example.js
├── playwright.json
├── remote.js
├── responsiveDemo.json
├── responsiveDemoAlias
├── responsiveTest.json
├── runFromNode.js
└── special_cases
│ ├── scrollToSelector.html
│ └── scrollToSelector_puppet.js
└── core
├── command
└── report_spec.js
├── runner_spec.js
└── util
├── backstop.json
├── compare
├── compare-hash_spec.js
├── compare-resemble_spec.js
├── compare_spec.js
├── refImage-1-optimized.png
├── refImage-1.png
├── refImage-2.png
└── refImage-3.png
├── engineErrors_spec.js
├── extendConfig_spec.js
├── fixtures
├── engineErrorsFail.json
└── engineErrorsSuccess.json
├── makeConfig_it_spec.js
├── makeConfig_spec.js
├── remote_spec.js
└── runDocker_spec.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | indent_size = 2
11 |
12 | [*.json]
13 | indent_size = 2
14 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | index_bundle.js
2 | config.js
3 | diff.js
4 | diverged.js
5 | divergedWorker.js
6 | **/examples/**
7 | **/old_splash_page_v2.0/**
8 | **/dist/**
9 | **/angular.min.js
10 | **/js/vendor/**
11 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-semistandard",
3 | "rules": {
4 | "no-multi-str": 0
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.github/workflows/_backstop-ci-runner.yml:
--------------------------------------------------------------------------------
1 | name: 👀 Backstop CI Runner
2 | run-name: "CI running on ${{ github.event_name == 'pull_request' && format('PR #{0}: {1}', github.event.pull_request.number, github.event.pull_request.title) || format('latest {0}', github.ref_name) }}"
3 |
4 | on:
5 | workflow_dispatch:
6 | pull_request:
7 | branches: [master, develop]
8 | push:
9 | branches: [master, develop]
10 |
11 | permissions:
12 | actions: write
13 | checks: write
14 | contents: write
15 | pull-requests: write
16 | packages: write
17 |
18 | env:
19 | BRANCH_NAME: ${{ github.event.pull_request.head.sha || github.head_ref || github.ref_name }}
20 |
21 | jobs:
22 | backstop-reference-test:
23 | name: Backstop reference test
24 | uses: ./.github/workflows/backstop-reference-test.yml
25 |
26 |
--------------------------------------------------------------------------------
/.github/workflows/backstop-publish.yml:
--------------------------------------------------------------------------------
1 | name: Backstop Publish
2 | #doesnt work :(
3 |
4 | on:
5 | workflow_dispatch:
6 | workflow_call:
7 |
8 |
9 | permissions:
10 | actions: write
11 | checks: write
12 | contents: write
13 | pull-requests: write
14 | packages: write
15 |
16 | env:
17 | BRANCH_NAME: ${{ github.event.pull_request.head.sha || github.head_ref || github.ref_name }}
18 | NODE_VERSION: 20
19 |
20 | jobs:
21 | backstop-publish-flow:
22 | name: publish-flow
23 | runs-on: ubuntu-latest
24 | steps:
25 | - name: publish to npm
26 | uses: ./.github/workflows/npm-push.yml
27 |
28 | - name: publish to dockerhub
29 | uses: ./.github/workflows/dockerhub-build-push.yml
30 |
--------------------------------------------------------------------------------
/.github/workflows/backstop-reference-test.yml:
--------------------------------------------------------------------------------
1 | name: 🛞 Backstop Reference Test
2 |
3 | on:
4 | workflow_dispatch:
5 | workflow_call:
6 |
7 | permissions:
8 | actions: write
9 | contents: write
10 | pull-requests: write
11 |
12 | env:
13 | NODE_VERSION: 20
14 |
15 | jobs:
16 | reference-test:
17 | name: reference-test
18 | runs-on: ubuntu-latest
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v4
22 | with:
23 | fetch-depth: 1
24 | ref: ${{ github.event.pull_request.head.sha || github.ref }}
25 |
26 | - name: Setup Node & Cache
27 | uses: actions/setup-node@v4
28 | with:
29 | cache: "npm"
30 | cache-dependency-path: package-lock.json
31 |
32 | - name: Install
33 | run: npm ci
34 |
35 | - name: "execute test"
36 | run: npm run reference-test
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.github/workflows/dockerhub-build-push.yml:
--------------------------------------------------------------------------------
1 | name: 🐳 Docker Hub Build & Push
2 |
3 | on:
4 | workflow_dispatch:
5 | workflow_call:
6 |
7 | permissions:
8 | actions: write
9 | checks: write
10 | contents: write
11 | pull-requests: write
12 | packages: write
13 |
14 | env:
15 | NODE_VERSION: 20
16 |
17 | jobs:
18 | build-and-push-image:
19 | runs-on: ubuntu-latest
20 |
21 | steps:
22 | - name: ⇣ Checkout
23 | uses: actions/checkout@v4
24 | with:
25 | fetch-depth: 1
26 | ref: ${{ github.event.pull_request.head.sha || github.ref }}
27 |
28 | - name: 🏷️ Set Docker Image Tag
29 | run: |
30 | echo "PV=$(cat package.json | jq -r '.version')" >> $GITHUB_ENV
31 |
32 | - name: Log in to the Container registry
33 | uses: docker/login-action@v3
34 | with:
35 | username: ${{ secrets.DOCKERHUB_USERNAME }}
36 | password: ${{ secrets.DOCKERHUB_TOKEN }}
37 |
38 | - name: ⬢ Setup Node & Cache
39 | uses: actions/setup-node@v4
40 | with:
41 | cache: "npm"
42 | cache-dependency-path: package-lock.json
43 |
44 | - name: ↧ Install
45 | run: npm ci --verbose --foreground-scripts
46 |
47 | - name: 🚢 Build Docker Builder
48 | run: |
49 | npm run --verbose --foreground-scripts init-docker-builder
50 |
51 | - name: 🐳 Build & Push to Docker Hub
52 | run: |
53 | docker buildx build --push --platform linux/amd64,linux/arm64 -t backstopjs/backstopjs:$PV -t backstopjs/backstopjs:latest --build-arg BACKSTOPJS_VERSION=$PV docker
54 |
--------------------------------------------------------------------------------
/.github/workflows/npm-push.yml:
--------------------------------------------------------------------------------
1 | name: 📦 NPM Push
2 |
3 | on:
4 | workflow_dispatch:
5 | workflow_call:
6 |
7 | permissions:
8 | actions: write
9 | checks: write
10 | contents: write
11 | pull-requests: write
12 | packages: write
13 |
14 | env:
15 | BRANCH_NAME: ${{ github.event.pull_request.head.sha || github.head_ref || github.ref_name }}
16 | NODE_VERSION: 20
17 |
18 | jobs:
19 | publish:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 | with:
25 | fetch-depth: 1
26 | ref: ${{ github.event.pull_request.head.sha || github.ref }}
27 |
28 | - name: Setup Node & Cache
29 | uses: actions/setup-node@v4
30 | with:
31 | cache: "npm"
32 | cache-dependency-path: package-lock.json
33 |
34 | - run: npm ci
35 |
36 | - uses: JS-DevTools/npm-publish@v3
37 | with:
38 | token: ${{ secrets.NPM_I2_TOKEN }}
39 |
--------------------------------------------------------------------------------
/.github/workflows/test-build-pub-npm-dockerhub.yml:
--------------------------------------------------------------------------------
1 | name: Test build & publish npm + docker
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | permissions:
7 | actions: write
8 | checks: write
9 | contents: write
10 | pull-requests: write
11 | packages: write
12 |
13 | env:
14 | NODE_VERSION: 20
15 |
16 | jobs:
17 | test-push-npm-dockerhub:
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - run: echo "start workflow"
22 |
23 | - name: Checkout
24 | uses: actions/checkout@v2
25 |
26 | - name: reference test
27 | uses: ./.github/actions/backstop-reference-test.yml
28 |
29 |
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | # Logs
4 | logs
5 | *.log
6 |
7 | # Runtime data
8 | pids
9 | *.pid
10 | *.seed
11 | *.cache
12 |
13 | # Dependency directory
14 | # Commenting this out is preferred by some people, see
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
16 | node_modules
17 | yarn.lock
18 |
19 | # Users Environment Variables
20 | .lock-wscript
21 |
22 | *.sublime*
23 | .idea
24 |
25 | compare/bower_components/**/.*
26 | examples/**/html_report/
27 |
28 | **/backstop_data/bitmaps_test/
29 | **/backstop_data/html_report/
30 | **/backstop_data/json_report/
31 | **/backstop_data/reports/
32 | compare/config.js
33 | capture/config.json
34 |
35 | compare/output/bitmaps_reference/
36 |
37 | compare/output/bitmaps_test/
38 |
39 | compare/output/config\.js
40 |
41 | \.vscode/
42 |
43 | # Tests
44 | *integrationTestDir
45 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | image: node:$NODEJS
2 |
3 | # Select what we should cache
4 | cache:
5 | key: ${CI_COMMIT_REF_SLUG}
6 | paths:
7 | - node_modules/
8 |
9 | before_script:
10 | - node -v
11 | - npm -v
12 | - npm install
13 |
14 | tests:
15 | script:
16 | - npm run lint
17 | - npm run unit-test
18 | parallel:
19 | matrix:
20 | - NODEJS:
21 | - 14
22 | - 16
23 | - 18
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | addons: # get google-chrome [stable|beta]
2 | chrome: beta
3 | language: node_js
4 | node_js:
5 | - 14
6 | - 16
7 | install:
8 | - npm install
9 | script:
10 | - npm run lint && npm run unit-test
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to this project
2 |
3 | Thank you for contributing to developer happiness all over the world!
4 |
5 | For notes on contributing to BackstopJS please see [Developing, bug fixing, contributing...](./README.md#developing-bug-fixing-contributing)
6 |
7 | ☮️
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Garris Shipon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/assets/BackstopJS.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/BackstopJS.pdf
--------------------------------------------------------------------------------
/assets/BackstopJS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/BackstopJS.png
--------------------------------------------------------------------------------
/assets/approve_feature_hilite_sm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/approve_feature_hilite_sm.png
--------------------------------------------------------------------------------
/assets/backstop_banner.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/backstop_banner.jpg
--------------------------------------------------------------------------------
/assets/backstop_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/backstop_banner.png
--------------------------------------------------------------------------------
/assets/backstopjs_new_ui_.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/backstopjs_new_ui_.png
--------------------------------------------------------------------------------
/assets/cli-report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/cli-report.png
--------------------------------------------------------------------------------
/assets/duskBg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/duskBg.png
--------------------------------------------------------------------------------
/assets/duskBg1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/duskBg1.png
--------------------------------------------------------------------------------
/assets/github-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/github-icon.png
--------------------------------------------------------------------------------
/assets/lemurButt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/lemurButt.png
--------------------------------------------------------------------------------
/assets/lemurFace.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/lemurFace.pdf
--------------------------------------------------------------------------------
/assets/lemurFace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/lemurFace.png
--------------------------------------------------------------------------------
/assets/lemurFace2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/lemurFace2.png
--------------------------------------------------------------------------------
/assets/memes/do_not_razz_the_lemur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/memes/do_not_razz_the_lemur.png
--------------------------------------------------------------------------------
/assets/memes/im-in-ur-webapps-checkin-ur-scr33ns-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/memes/im-in-ur-webapps-checkin-ur-scr33ns-1.jpg
--------------------------------------------------------------------------------
/assets/memes/im-in-ur-webapps-checking-ur-screens.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/memes/im-in-ur-webapps-checking-ur-screens.jpg
--------------------------------------------------------------------------------
/assets/memes/lemur_template_square.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/memes/lemur_template_square.png
--------------------------------------------------------------------------------
/assets/memes/lemur_template_wide.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/memes/lemur_template_wide.png
--------------------------------------------------------------------------------
/assets/osRenderDifference.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/osRenderDifference.png
--------------------------------------------------------------------------------
/assets/scoochAndTomster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/assets/scoochAndTomster.png
--------------------------------------------------------------------------------
/capture/config.default.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "backstop_default",
3 | "viewports": [
4 | {
5 | "label": "phone",
6 | "width": 320,
7 | "height": 480
8 | },
9 | {
10 | "label": "tablet",
11 | "width": 1024,
12 | "height": 768
13 | }
14 | ],
15 | "onBeforeScript": "puppet/onBefore.js",
16 | "onReadyScript": "puppet/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "BackstopJS Homepage",
20 | "cookiePath": "backstop_data/engine_scripts/cookies.json",
21 | "url": "https://garris.github.io/BackstopJS/",
22 | "referenceUrl": "",
23 | "readyEvent": "",
24 | "readySelector": "",
25 | "delay": 0,
26 | "hideSelectors": [],
27 | "removeSelectors": [],
28 | "hoverSelector": "",
29 | "clickSelector": "",
30 | "postInteractionWait": 0,
31 | "selectors": [],
32 | "selectorExpansion": true,
33 | "expect": 0,
34 | "misMatchThreshold" : 0.1,
35 | "requireSameDimensions": true
36 | }
37 | ],
38 | "paths": {
39 | "bitmaps_reference": "backstop_data/bitmaps_reference",
40 | "bitmaps_test": "backstop_data/bitmaps_test",
41 | "engine_scripts": "backstop_data/engine_scripts",
42 | "html_report": "backstop_data/html_report",
43 | "ci_report": "backstop_data/ci_report"
44 | },
45 | "report": ["browser"],
46 | "engine": "puppeteer",
47 | "engineOptions": {
48 | "args": ["--no-sandbox"]
49 | },
50 | "asyncCaptureLimit": 5,
51 | "asyncCompareLimit": 50,
52 | "debug": false,
53 | "debugWindow": false
54 | }
55 |
--------------------------------------------------------------------------------
/capture/engine_scripts/cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": ".www.yourdomain.com",
4 | "path": "/",
5 | "name": "yourCookieName",
6 | "value": "yourCookieValue",
7 | "expirationDate": 1798790400,
8 | "hostOnly": false,
9 | "httpOnly": false,
10 | "secure": false,
11 | "session": false,
12 | "sameSite": "Lax"
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/capture/engine_scripts/imageStub.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/engine_scripts/imageStub.jpg
--------------------------------------------------------------------------------
/capture/engine_scripts/playwright/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario) => {
2 | const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3 | const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4 | const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5 | const scrollToSelector = scenario.scrollToSelector;
6 | const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7 |
8 | if (keyPressSelector) {
9 | for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10 | await page.waitForSelector(keyPressSelectorItem.selector);
11 | await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12 | }
13 | }
14 |
15 | if (hoverSelector) {
16 | for (const hoverSelectorIndex of [].concat(hoverSelector)) {
17 | await page.waitForSelector(hoverSelectorIndex);
18 | await page.hover(hoverSelectorIndex);
19 | }
20 | }
21 |
22 | if (clickSelector) {
23 | for (const clickSelectorIndex of [].concat(clickSelector)) {
24 | await page.waitForSelector(clickSelectorIndex);
25 | await page.click(clickSelectorIndex);
26 | }
27 | }
28 |
29 | if (postInteractionWait) {
30 | if (parseInt(postInteractionWait) > 0) {
31 | await page.waitForTimeout(postInteractionWait);
32 | } else {
33 | await page.waitForSelector(postInteractionWait);
34 | }
35 | }
36 |
37 | if (scrollToSelector) {
38 | await page.waitForSelector(scrollToSelector);
39 | await page.evaluate(scrollToSelector => {
40 | document.querySelector(scrollToSelector).scrollIntoView();
41 | }, scrollToSelector);
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/capture/engine_scripts/playwright/interceptImages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * INTERCEPT IMAGES
3 | * Listen to all requests. If a request matches IMAGE_URL_RE
4 | * then stub the image with data from IMAGE_STUB_URL
5 | *
6 | * Use this in an onBefore script E.G.
7 | ```
8 | module.exports = async function(page, scenario) {
9 | require('./interceptImages')(page, scenario);
10 | }
11 | ```
12 | *
13 | */
14 |
15 | const fs = require('fs');
16 | const path = require('path');
17 |
18 | const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i;
19 | const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg');
20 | const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL);
21 | const HEADERS_STUB = {};
22 |
23 | module.exports = async function (page, scenario) {
24 | page.route(IMAGE_URL_RE, route => {
25 | route.fulfill({
26 | body: IMAGE_DATA_BUFFER,
27 | headers: HEADERS_STUB,
28 | status: 200
29 | });
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/capture/engine_scripts/playwright/loadCookies.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (browserContext, scenario) => {
4 | let cookies = [];
5 | const cookiePath = scenario.cookiePath;
6 |
7 | // Read Cookies from File, if exists
8 | if (fs.existsSync(cookiePath)) {
9 | cookies = JSON.parse(fs.readFileSync(cookiePath));
10 | }
11 |
12 | // Add cookies to browser
13 | browserContext.addCookies(cookies);
14 |
15 | console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
16 | };
17 |
--------------------------------------------------------------------------------
/capture/engine_scripts/playwright/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, viewport, isReference, browserContext) => {
2 | await require('./loadCookies')(browserContext, scenario);
3 | };
4 |
--------------------------------------------------------------------------------
/capture/engine_scripts/playwright/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, viewport, isReference, browserContext) => {
2 | console.log('SCENARIO > ' + scenario.label);
3 | await require('./clickAndHoverHelper')(page, scenario);
4 |
5 | // add more ready handlers here...
6 | };
7 |
--------------------------------------------------------------------------------
/capture/engine_scripts/playwright/overrideCSS.js:
--------------------------------------------------------------------------------
1 | /**
2 | * OVERRIDE CSS
3 | * Apply this CSS to the loaded page, as a way to override styles.
4 | *
5 | * Use this in an onReady script E.G.
6 | ```
7 | module.exports = async function(page, scenario) {
8 | await require('./overrideCSS')(page, scenario);
9 | }
10 | ```
11 | *
12 | */
13 |
14 | const BACKSTOP_TEST_CSS_OVERRIDE = `
15 | html {
16 | background-image: none;
17 | }
18 | `;
19 |
20 | module.exports = async (page, scenario) => {
21 | // inject arbitrary css to override styles
22 | await page.addStyleTag({
23 | content: BACKSTOP_TEST_CSS_OVERRIDE
24 | });
25 |
26 | console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label);
27 | };
28 |
--------------------------------------------------------------------------------
/capture/engine_scripts/puppet/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario) => {
2 | const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3 | const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4 | const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5 | const scrollToSelector = scenario.scrollToSelector;
6 | const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7 |
8 | if (keyPressSelector) {
9 | for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10 | await page.waitForSelector(keyPressSelectorItem.selector);
11 | await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12 | }
13 | }
14 |
15 | if (hoverSelector) {
16 | for (const hoverSelectorIndex of [].concat(hoverSelector)) {
17 | await page.waitForSelector(hoverSelectorIndex);
18 | await page.hover(hoverSelectorIndex);
19 | }
20 | }
21 |
22 | if (clickSelector) {
23 | for (const clickSelectorIndex of [].concat(clickSelector)) {
24 | await page.waitForSelector(clickSelectorIndex);
25 | await page.click(clickSelectorIndex);
26 | }
27 | }
28 |
29 | if (postInteractionWait) {
30 | await new Promise(resolve => {
31 | setTimeout(resolve, postInteractionWait);
32 | });
33 | }
34 |
35 | if (scrollToSelector) {
36 | await page.waitForSelector(scrollToSelector);
37 | await page.evaluate(scrollToSelector => {
38 | document.querySelector(scrollToSelector).scrollIntoView();
39 | }, scrollToSelector);
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/capture/engine_scripts/puppet/interceptImages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * INTERCEPT IMAGES
3 | * Listen to all requests. If a request matches IMAGE_URL_RE
4 | * then stub the image with data from IMAGE_STUB_URL
5 | *
6 | * Use this in an onBefore script E.G.
7 | ```
8 | module.exports = async function(page, scenario) {
9 | require('./interceptImages')(page, scenario);
10 | }
11 | ```
12 | *
13 | */
14 |
15 | const fs = require('fs');
16 | const path = require('path');
17 |
18 | const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i;
19 | const IMAGE_STUB_URL = path.resolve(__dirname, '../imageStub.jpg');
20 | const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL);
21 | const HEADERS_STUB = {};
22 |
23 | module.exports = async function (page, scenario) {
24 | const intercept = async (request, targetUrl) => {
25 | if (IMAGE_URL_RE.test(request.url())) {
26 | await request.respond({
27 | body: IMAGE_DATA_BUFFER,
28 | headers: HEADERS_STUB,
29 | status: 200
30 | });
31 | } else {
32 | request.continue();
33 | }
34 | };
35 | await page.setRequestInterception(true);
36 | page.on('request', intercept);
37 | };
38 |
--------------------------------------------------------------------------------
/capture/engine_scripts/puppet/loadCookies.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (page, scenario) => {
4 | let cookies = [];
5 | const cookiePath = scenario.cookiePath;
6 |
7 | // READ COOKIES FROM FILE IF EXISTS
8 | if (fs.existsSync(cookiePath)) {
9 | cookies = JSON.parse(fs.readFileSync(cookiePath));
10 | }
11 |
12 | // MUNGE COOKIE DOMAIN
13 | cookies = cookies.map(cookie => {
14 | if (cookie.domain.startsWith('http://') || cookie.domain.startsWith('https://')) {
15 | cookie.url = cookie.domain;
16 | } else {
17 | cookie.url = 'https://' + cookie.domain;
18 | }
19 | delete cookie.domain;
20 | return cookie;
21 | });
22 |
23 | // SET COOKIES
24 | const setCookies = async () => {
25 | return Promise.all(
26 | cookies.map(async (cookie) => {
27 | await page.setCookie(cookie);
28 | })
29 | );
30 | };
31 | await setCookies();
32 | console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
33 | };
34 |
--------------------------------------------------------------------------------
/capture/engine_scripts/puppet/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | await require('./loadCookies')(page, scenario);
3 | };
4 |
--------------------------------------------------------------------------------
/capture/engine_scripts/puppet/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | console.log('SCENARIO > ' + scenario.label);
3 | await require('./clickAndHoverHelper')(page, scenario);
4 |
5 | // add more ready handlers here...
6 | };
7 |
--------------------------------------------------------------------------------
/capture/engine_scripts/puppet/overrideCSS.js:
--------------------------------------------------------------------------------
1 | const BACKSTOP_TEST_CSS_OVERRIDE = 'html {background-image: none;}';
2 |
3 | module.exports = async (page, scenario) => {
4 | // inject arbitrary css to override styles
5 | await page.evaluate(`window._styleData = '${BACKSTOP_TEST_CSS_OVERRIDE}'`);
6 | await page.evaluate(() => {
7 | const style = document.createElement('style');
8 | style.type = 'text/css';
9 | const styleNode = document.createTextNode(window._styleData);
10 | style.appendChild(styleNode);
11 | document.head.appendChild(style);
12 | });
13 |
14 | console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label);
15 | };
16 |
--------------------------------------------------------------------------------
/capture/resources/hiddenSelector_noun_63405.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/resources/hiddenSelector_noun_63405.png
--------------------------------------------------------------------------------
/capture/resources/notFound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/resources/notFound.png
--------------------------------------------------------------------------------
/capture/resources/notVisible.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/resources/notVisible.png
--------------------------------------------------------------------------------
/capture/resources/selectorNotFound_noun_164558_cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/resources/selectorNotFound_noun_164558_cc.png
--------------------------------------------------------------------------------
/capture/resources/unexpectedError.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/resources/unexpectedError.png
--------------------------------------------------------------------------------
/capture/resources/unexpectedErrorSm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/capture/resources/unexpectedErrorSm.png
--------------------------------------------------------------------------------
/cli/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const parseArgs = require('minimist');
4 | const usage = require('./usage');
5 | const version = require('../package.json').version;
6 | const runner = require('../core/runner');
7 |
8 | main();
9 |
10 | function main () {
11 | const argsOptions = parseArgs(process.argv.slice(2), {
12 | boolean: ['h', 'help', 'v', 'version', 'i', 'docker'],
13 | string: ['config'],
14 | default: {
15 | config: 'backstop.json'
16 | }
17 | });
18 |
19 | // Catch errors from failing promises
20 | process.on('unhandledRejection', function (error) {
21 | console.error(error && error.stack);
22 | });
23 |
24 | if (argsOptions.h || argsOptions.help) {
25 | console.log(usage);
26 | return;
27 | }
28 |
29 | if (argsOptions.v || argsOptions.version) {
30 | console.log('BackstopJS v' + version);
31 | return;
32 | }
33 |
34 | const commandName = argsOptions._[0];
35 |
36 | if (!commandName) {
37 | console.log(usage);
38 | } else {
39 | console.log('BackstopJS v' + version);
40 | runner(commandName, argsOptions).catch(function () {
41 | process.exitCode = 1;
42 | });
43 |
44 | process.on('uncaughtException', function (err) {
45 | console.log('Uncaught exception:', err.message, err.stack);
46 | throw err;
47 | });
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/compare/README.md:
--------------------------------------------------------------------------------
1 | HTML report resource bundle
2 | ====
3 |
4 | This directory contains the source files for the BackstopJS report UI.
5 |
6 | To build the React project run...
7 |
8 | ```
9 | npm run build-compare
10 | ```
11 |
12 | This will generate `/compare/output/index_bundle.js`.
13 |
14 | `/compare/output/index_bundle.js` contains all styles and js for the HTML report. In normal BackstopJS operation this file bundle will be copied into the correct HTML report directory during a test flow (e.g. when running `backstop test`) after bitmap generation has completed. See: `/core/command/report.js` writeBrowserReport() method for details on this mechanism.
15 |
16 | Note: The files `diverged.js` & `diff.js` are copied from `node_modules` to `/compare/output/` during build.
17 |
--------------------------------------------------------------------------------
/compare/output/a96f14595379b7c348d66e115ec65a93.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/a96f14595379b7c348d66e115ec65a93.png
--------------------------------------------------------------------------------
/compare/output/assets/fonts/Lato-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/assets/fonts/Lato-Bold.ttf
--------------------------------------------------------------------------------
/compare/output/assets/fonts/Lato-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/assets/fonts/Lato-Regular.ttf
--------------------------------------------------------------------------------
/compare/output/assets/fonts/lato-bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/assets/fonts/lato-bold-webfont.woff
--------------------------------------------------------------------------------
/compare/output/assets/fonts/lato-bold-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/assets/fonts/lato-bold-webfont.woff2
--------------------------------------------------------------------------------
/compare/output/assets/fonts/lato-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/assets/fonts/lato-regular-webfont.woff
--------------------------------------------------------------------------------
/compare/output/assets/fonts/lato-regular-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/assets/fonts/lato-regular-webfont.woff2
--------------------------------------------------------------------------------
/compare/output/b815e28b1e230cff6e9d7b749edcd562.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/output/b815e28b1e230cff6e9d7b749edcd562.png
--------------------------------------------------------------------------------
/compare/output/divergedWorker.js:
--------------------------------------------------------------------------------
1 | importScripts('diff.js');
2 | importScripts('diverged.js');
3 | self.addEventListener('message', function(e) {
4 | self.postMessage(diverged(...e.data.divergedInput));
5 | self.close();
6 | }, false);
7 |
--------------------------------------------------------------------------------
/compare/output/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | BackstopJS Report
11 |
12 |
36 |
37 |
38 |
39 |
40 |
41 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/compare/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc",
3 | "parserOptions": {
4 | "ecmaFeatures": {
5 | "jsx": true
6 | },
7 | "sourceType": "module"
8 | },
9 | "env": {
10 | "browser": true,
11 | "node": false
12 | },
13 | "plugins": [
14 | "react"
15 | ],
16 | "rules": {
17 | "react/jsx-uses-react": 2,
18 | "react/jsx-uses-vars": 2
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/compare/src/assets/icons/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/src/assets/icons/close.png
--------------------------------------------------------------------------------
/compare/src/assets/icons/iconDown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/src/assets/icons/iconDown.png
--------------------------------------------------------------------------------
/compare/src/assets/icons/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/src/assets/icons/search.png
--------------------------------------------------------------------------------
/compare/src/assets/icons/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/src/assets/icons/settings.png
--------------------------------------------------------------------------------
/compare/src/assets/icons/settings_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/src/assets/icons/settings_2.png
--------------------------------------------------------------------------------
/compare/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/compare/src/assets/images/logo.png
--------------------------------------------------------------------------------
/compare/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | // ESLint
4 | /* eslint-disable no-unused-vars */
5 | import { StickyContainer } from 'react-sticky';
6 |
7 | import Header from './ecosystems/Header';
8 | import List from './ecosystems/List';
9 | import ScrubberModal from './ecosystems/ScrubberModal';
10 | import LogModal from './ecosystems/LogModal';
11 |
12 | const Wrapper = styled.section`
13 | padding: 0 30px;
14 | `;
15 |
16 | export default class App extends React.Component {
17 | render () {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/ButtonFilter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { colors, fonts, shadows } from '../../styles';
5 |
6 | const Button = styled.button`
7 | font-size: 20px;
8 | font-family: ${fonts.latoRegular};
9 | flex: 0 0 auto;
10 | margin: 0;
11 | background-color: ${colors.white};
12 | border: none;
13 | border-radius: 3px;
14 | box-shadow: ${props => (props.selected ? 'none' : shadows.shadow01)};
15 | color: ${colors.primaryText};
16 | margin-right: 15px;
17 | padding: 0px 30px;
18 | opacity: ${props => (props.selected ? '1' : '0.5')};
19 | outline: none;
20 | height: 100%;
21 | transition: all 0.3s ease-in-out;
22 |
23 | &:hover {
24 | cursor: pointer;
25 | box-shadow: ${props => (!props.selected ? shadows.shadow02 : '')};
26 | }
27 |
28 | &.pass {
29 | background-color: ${colors.green};
30 | color: ${colors.white};
31 | }
32 |
33 | &.fail {
34 | background-color: ${colors.red};
35 | color: ${colors.white};
36 | }
37 | `;
38 |
39 | export default class ButtonFilter extends React.Component {
40 | render () {
41 | const { count, label, status } = this.props;
42 |
43 | return (
44 |
49 | {status !== 'all' ? count : ''} {label}
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/ButtonSettings.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { colors, fonts, shadows } from '../../styles';
5 |
6 | import settingsIcon from '../../assets/icons/settings.png';
7 |
8 | const Button = styled.button`
9 | border: none;
10 | height: 100%;
11 | border-radius: 3px;
12 | background: ${colors.lightGray};
13 | margin-left: 15px;
14 | padding: 0 20px;
15 | box-shadow: ${shadows.shadow01};
16 | transition: all 0.3s ease-in-out;
17 |
18 | &.active {
19 | box-shadow: none;
20 | opacity: 0.6;
21 | }
22 |
23 | &:hover {
24 | cursor: pointer;
25 | box-shadow: ${props => (!props.selected ? shadows.shadow02 : '')};
26 | }
27 |
28 | &:focus {
29 | outline: none;
30 | }
31 |
32 | .icon {
33 | height: 18px;
34 | width: 18px;
35 | display: block;
36 | background-image: url(${settingsIcon});
37 | background-size: 100%;
38 | background-repeat: no-repeat;
39 | background-position: center;
40 | margin: 0 auto;
41 | padding-bottom: 5px;
42 | }
43 |
44 | .label {
45 | font-family: ${fonts.latoRegular};
46 | color: ${colors.secondaryText};
47 | }
48 | `;
49 |
50 | export default class ButtonSettings extends React.Component {
51 | render () {
52 | const isActive = this.props.active ? 'active' : '';
53 |
54 | return (
55 |
56 |
57 | {/* settings */}
58 |
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/DiffDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { colors, fonts } from '../../styles';
4 |
5 | const Label = styled.span`
6 | font-family: ${fonts.latoRegular};
7 | color: ${colors.secondaryText};
8 | font-size: 14px;
9 | padding-right: 8px;
10 | `;
11 |
12 | const Value = styled.span`
13 | font-family: ${fonts.latoBold};
14 | color: ${colors.primaryText};
15 | font-size: 14px;
16 | padding-right: 20px;
17 | `;
18 |
19 | export default class DiffDetails extends React.Component {
20 | render () {
21 | const { diff, suppress } = this.props;
22 | if (!diff || suppress) {
23 | return null;
24 | }
25 |
26 | return (
27 |
28 | diff%:
29 | {diff.misMatchPercentage}
30 | diff-x:
31 | {diff.dimensionDifference.width}
32 | diff-y:
33 | {diff.dimensionDifference.height}
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/ErrorMessages.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import styled from 'styled-components';
4 | import { colors, fonts } from '../../styles';
5 |
6 | const DetailsPanel = styled.div`
7 | background: transparent;
8 | display: ${props => (props.display ? 'block' : 'none')};
9 | padding: 10px;
10 | font-family: ${fonts.latoRegular};
11 | color: ${colors.secondaryText};
12 | `;
13 |
14 | const ErrorMsg = styled.p`
15 | word-wrap: break-word;
16 | font-family: monospace;
17 | background: rgb(251, 234, 234);
18 | padding: 2ex;
19 | color: brown;
20 | display: ${props => (props.display ? 'block' : 'none')};
21 | `;
22 |
23 | class ErrorMessages extends React.Component {
24 | constructor (props) {
25 | super(props);
26 | this.state = {};
27 | }
28 |
29 | render () {
30 | const backstopError = this.props.info.error;
31 | const engineError = this.props.info.engineErrorMsg;
32 | const display = !!engineError || !!backstopError;
33 |
34 | return (
35 |
36 | ENGINE ERROR: {engineError}
37 |
38 | BACKSTOP ERROR: {backstopError}
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | const mapStateToProps = state => {
46 | return {
47 | settings: state.layoutSettings
48 | };
49 | };
50 |
51 | const ErrorMessagesContainer = connect(mapStateToProps)(ErrorMessages);
52 |
53 | export default ErrorMessagesContainer;
54 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/IdContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import styled from 'styled-components';
4 |
5 | import { colors, fonts } from '../../styles';
6 |
7 | const IdTitle = styled.h3`
8 | font-size: 14px;
9 | font-family: ${fonts.arial};
10 | font-weight: normal;
11 | font-style: normal;
12 | margin: 0;
13 | color: ${colors.secondaryText};
14 | flex: 1 0 auto;
15 | padding-left: 15px;
16 | margin-left: 15px;
17 | margin-top: 7px;
18 | position: relative;
19 |
20 | :before {
21 | content: '';
22 | width: 2px;
23 | height: 35px;
24 | background: ${colors.borderGray};
25 | display: block;
26 | position: absolute;
27 | left: 0;
28 | top: -10px;
29 | }
30 | `;
31 |
32 | class IdConfig extends React.Component {
33 | render () {
34 | return {this.props.idConfig} ;
35 | }
36 | }
37 |
38 | const mapStateToProps = state => {
39 | return {
40 | idConfig: state.suiteInfo.idConfig
41 | };
42 | };
43 |
44 | const IdContainer = connect(mapStateToProps)(IdConfig);
45 |
46 | export default IdContainer;
47 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/InputTextSearch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { colors, fonts } from '../../styles';
5 |
6 | import searchIcon from '../../assets/icons/search.png';
7 |
8 | const Input = styled.input`
9 | display: block;
10 | height: 100%;
11 | border: none;
12 | font-size: 16px;
13 | background-color: ${colors.lightGray};
14 | padding: 0 10px 0 55px;
15 | font-family: ${fonts.latoRegular};
16 | width: 100%;
17 | box-sizing: border-box;
18 | border-radius: 3px;
19 | background-image: url(${searchIcon});
20 | background-repeat: no-repeat;
21 | background-position-x: 15px;
22 | background-position-y: calc(100% / 2);
23 | background-size: 22px;
24 |
25 | &:focus {
26 | outline: none;
27 | }
28 |
29 | &::placeholder {
30 | font-family: ${fonts.arial};
31 | font-weight: 400;
32 | font-style: italic;
33 | color: ${colors.secondaryText};
34 | }
35 | `;
36 |
37 | export default class ButtonFilter extends React.Component {
38 | render () {
39 | return (
40 |
44 | );
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/Logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import LogoImg from '../../assets/images/logo.png';
5 |
6 | const LogoImage = styled.img`
7 | display: block;
8 | height: 35px;
9 | `;
10 |
11 | export default class Logo extends React.Component {
12 | render () {
13 | return (
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/SettingOption.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import ToggleButton from 'react-toggle-button';
4 |
5 | import { colors, fonts } from '../../styles';
6 |
7 | const WrapperOption = styled.div`
8 | display: flex;
9 | align-items: center;
10 | justify-content: space-between;
11 | padding: 10px 0;
12 |
13 | span {
14 | padding-right: 10px;
15 | text-align: left;
16 | font-family: ${fonts.latoRegular};
17 | color: ${colors.primaryText};
18 | font-size: 14px;
19 | }
20 | `;
21 |
22 | export default class SettingOption extends React.Component {
23 | render () {
24 | const { label, value, onToggle } = this.props;
25 |
26 | return (
27 |
28 | {label}
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/SuiteName.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import styled from 'styled-components';
4 |
5 | import { colors, fonts } from '../../styles';
6 |
7 | const SuiteNameTitle = styled.h1`
8 | font-size: 26px;
9 | font-family: ${fonts.latoRegular};
10 | flex: 0 0 auto;
11 | margin: 0;
12 | color: ${colors.primaryText};
13 | `;
14 |
15 | class SuiteName extends React.Component {
16 | render () {
17 | return {this.props.suiteName} Report ;
18 | }
19 | }
20 |
21 | const mapStateToProps = state => {
22 | return {
23 | suiteName: state.suiteInfo.testSuiteName
24 | };
25 | };
26 |
27 | const SuiteNameContainer = connect(mapStateToProps)(SuiteName);
28 |
29 | export default SuiteNameContainer;
30 |
--------------------------------------------------------------------------------
/compare/src/components/atoms/UrlDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { colors, fonts } from '../../styles';
4 |
5 | const Label = styled.span`
6 | font-family: ${fonts.latoRegular};
7 | color: ${colors.secondaryText};
8 | font-size: 14px;
9 | padding-right: 8px;
10 | `;
11 |
12 | const Value = styled.span`
13 | font-family: ${fonts.latoBold};
14 | color: ${colors.primaryText};
15 | font-size: 14px;
16 | padding-right: 20px;
17 | `;
18 |
19 | const Link = styled.a`
20 | &::before {
21 | content: ${props => (props.withSeperator ? '"|"' : '')};
22 | margin: ${props => (props.withSeperator ? '0 10px' : '')};
23 | }
24 | `;
25 |
26 | export default class DiffDetails extends React.Component {
27 | render () {
28 | const { url, referenceUrl } = this.props;
29 | return (
30 |
31 | url:
32 |
33 |
34 | test
35 |
36 | {referenceUrl && (
37 |
38 | reference
39 |
40 | )}
41 |
42 |
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/compare/src/components/ecosystems/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Sticky } from 'react-sticky';
4 |
5 | import Topbar from '../organisms/Topbar';
6 | import Toolbar from '../organisms/Toolbar';
7 |
8 | const HeaderWrapper = styled.section`
9 | width: 100%;
10 | margin: 0 auto;
11 | padding: 15px 0;
12 | z-index: 999;
13 | box-sizing: border-box;
14 | position: relative;
15 | `;
16 |
17 | export default class Header extends React.Component {
18 | render () {
19 | return (
20 |
21 |
22 |
23 | {({
24 | isSticky,
25 | wasSticky,
26 | style,
27 | distanceFromTop,
28 | distanceFromBottom,
29 | calculatedHeight
30 | }) => {
31 | return ;
32 | }}
33 |
34 |
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/compare/src/components/ecosystems/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { connect } from 'react-redux';
4 |
5 | // organisms
6 | import TestCard from '../organisms/TestCard';
7 |
8 | const ListWrapper = styled.section`
9 | width: 100%;
10 | margin: 0 auto;
11 | margin-top: 20px;
12 | z-index: 1;
13 | `;
14 |
15 | class List extends React.Component {
16 | render () {
17 | const { tests, settings } = this.props;
18 | const onlyText =
19 | !settings.refImage && !settings.testImage && !settings.diffImage;
20 |
21 | return (
22 |
23 | {tests.map((test, i, arr) => (
24 |
32 | ))}
33 |
34 | );
35 | }
36 | }
37 |
38 | const mapStateToProps = state => {
39 | return {
40 | tests: state.tests.filtered,
41 | settings: state.layoutSettings
42 | };
43 | };
44 |
45 | const ListContainer = connect(mapStateToProps)(List);
46 |
47 | export default ListContainer;
48 |
--------------------------------------------------------------------------------
/compare/src/components/molecules/SettingsContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import styled from 'styled-components';
4 | // import { findTests } from '../../actions'
5 |
6 | // atoms
7 | import ButtonSettings from '../atoms/ButtonSettings';
8 |
9 | // molecules
10 | import SettingsPopup from './SettingsPopup';
11 |
12 | const SettingsWrapper = styled.div`
13 | flex: 0 0 auto;
14 | height: 100%;
15 | `;
16 |
17 | class SettingsPanel extends React.Component {
18 | constructor (props) {
19 | super(props);
20 |
21 | this.state = {
22 | popup: false
23 | };
24 | }
25 |
26 | onButtonClick () {
27 | this.setState({
28 | popup: !this.state.popup
29 | });
30 | }
31 |
32 | render () {
33 | const popupVisible = this.state.popup;
34 |
35 | return (
36 |
37 |
41 | {popupVisible && }
42 |
43 | );
44 | }
45 | }
46 |
47 | const mapStateToProps = state => {
48 | return {};
49 | };
50 |
51 | const mapDispatchToProps = dispatch => {
52 | return {
53 | // onChange: value => {
54 | // dispatch(findTests(value))
55 | // }
56 | };
57 | };
58 |
59 | const SettingsContainer = connect(mapStateToProps, mapDispatchToProps)(
60 | SettingsPanel
61 | );
62 |
63 | export default SettingsContainer;
64 |
--------------------------------------------------------------------------------
/compare/src/components/molecules/TextSearch.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import styled from 'styled-components';
4 | import { findTests, filterTests } from '../../actions';
5 |
6 | import InputTextSearch from '../atoms/InputTextSearch';
7 |
8 | const InputWrapper = styled.div`
9 | flex: 1 1 auto;
10 | height: 100%;
11 | `;
12 |
13 | class TextSearch extends React.Component {
14 | onChange (event) {
15 | const value = event.target.value;
16 |
17 | if (value.length > 0) {
18 | this.props.findTest(value);
19 | } else {
20 | this.props.filterTests(this.props.tests.filterStatus);
21 | }
22 | }
23 |
24 | render () {
25 | return (
26 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | const mapStateToProps = state => {
34 | return {
35 | tests: state.tests
36 | };
37 | };
38 |
39 | const mapDispatchToProps = dispatch => {
40 | return {
41 | findTest: value => {
42 | dispatch(findTests(value));
43 | },
44 | filterTests: status => {
45 | dispatch(filterTests(status));
46 | }
47 | };
48 | };
49 |
50 | const TextSearchContainer = connect(mapStateToProps, mapDispatchToProps)(
51 | TextSearch
52 | );
53 |
54 | export default TextSearchContainer;
55 |
--------------------------------------------------------------------------------
/compare/src/components/organisms/Toolbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import FiltersSwitchContainer from '../molecules/FiltersSwitch';
5 | import TextSearchContainer from '../molecules/TextSearch';
6 | import SettingsContainer from '../molecules/SettingsContainer';
7 |
8 | import { colors } from '../../styles';
9 |
10 | const ToolbarWrapper = styled.section`
11 | width: 100%;
12 | padding: 10px 30px;
13 | background: ${colors.bodyColor};
14 | height: 70px;
15 | display: flex;
16 | box-sizing: border-box;
17 |
18 | @media print {
19 | display: none;
20 | }
21 | `;
22 |
23 | export default class Toolbar extends React.Component {
24 | render () {
25 | return (
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/compare/src/components/organisms/Topbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { colors } from '../../styles';
5 |
6 | import SuiteNameContainer from '../atoms/SuiteName';
7 | import IdContainer from '../atoms/IdContainer';
8 | import Logo from '../atoms/Logo';
9 |
10 | const TopbarWrapper = styled.section`
11 | width: 100%;
12 | margin: 0 auto;
13 | display: flex;
14 | padding: 0 30px;
15 | align-items: center;
16 | box-sizing: border-box;
17 | flex-wrap: wrap;
18 | `;
19 |
20 | const Separator = styled.div`
21 | width: 100%;
22 | height: 3px;
23 | background: ${colors.borderGray};
24 | flex-basis: 100%;
25 | margin: 10px 0;
26 | `;
27 |
28 | export default class Topbar extends React.Component {
29 | render () {
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/compare/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import store from './store.js';
5 |
6 | import App from './components/App';
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 |
--------------------------------------------------------------------------------
/compare/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import tests from './tests';
3 | import suiteInfo from './suiteInfo';
4 | import layoutSettings from './layoutSettings';
5 | import scrubber from './scrubber';
6 | import logs from './logs';
7 |
8 | const rootReducer = combineReducers({
9 | suiteInfo,
10 | tests,
11 | scrubber,
12 | logs,
13 | layoutSettings
14 | });
15 |
16 | export default rootReducer;
17 |
--------------------------------------------------------------------------------
/compare/src/reducers/layoutSettings.js:
--------------------------------------------------------------------------------
1 | const visibilityFilter = (state = {}, action) => {
2 | switch (action.type) {
3 | case 'UPDATE_SETTINGS':
4 | return Object.assign({}, state, {
5 | [action.id]: !state[action.id]
6 | });
7 |
8 | case 'TOGGLE_ALL_IMAGES':
9 | return Object.assign({}, state, {
10 | refImage: action.value,
11 | testImage: action.value,
12 | diffImage: action.value
13 | });
14 |
15 | default:
16 | return state;
17 | }
18 | };
19 |
20 | export default visibilityFilter;
21 |
--------------------------------------------------------------------------------
/compare/src/reducers/logs.js:
--------------------------------------------------------------------------------
1 | const logs = (state = {}, action) => {
2 | switch (action.type) {
3 | case 'OPEN_LOG_MODAL':
4 | return Object.assign({}, state, {
5 | visible: true,
6 | logs: action.value
7 | });
8 |
9 | case 'CLOSE_LOG_MODAL':
10 | return Object.assign({}, state, {
11 | visible: false
12 | });
13 | default:
14 | return state;
15 | }
16 | };
17 |
18 | export default logs;
19 |
--------------------------------------------------------------------------------
/compare/src/reducers/suiteInfo.js:
--------------------------------------------------------------------------------
1 | const suiteInfo = (state = {}, action) => {
2 | switch (action.type) {
3 | case 'SET_VISIBILITY_FILTER':
4 | return action.filter;
5 | default:
6 | return state;
7 | }
8 | };
9 |
10 | export default suiteInfo;
11 |
--------------------------------------------------------------------------------
/compare/src/styles/index.js:
--------------------------------------------------------------------------------
1 | export const colors = {
2 | primaryText: '#4A4A4A',
3 | bodyColor: '#E2E7EA',
4 | secondaryText: '#787878',
5 | borderGray: '#D1D9DD',
6 | green: '#8BC34A',
7 | red: '#F44336',
8 | white: '#FFFFFF',
9 | cardWhite: '#FAFAFA',
10 | lightGray: '#EEEEEE',
11 | medGray: '#999999'
12 | };
13 |
14 | export const fonts = {
15 | latoRegular: 'latoregular',
16 | latoBold: 'latobold',
17 | arial: 'Arial'
18 | };
19 |
20 | export const shadows = {
21 | shadow01: '0 3px 6px 0 rgba(0,0,0,0.16)',
22 | shadow02:
23 | '0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.3)'
24 | };
25 |
--------------------------------------------------------------------------------
/compare/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | entry: path.join(__dirname, 'src', 'index.js'),
6 | devServer: {
7 | static: [
8 | {
9 | directory: path.resolve(__dirname, 'output'),
10 | serveIndex: true
11 | }, {
12 | directory: path.join(__dirname, '../test/configs/backstop_data/html_report'),
13 | publicPath: '/'
14 | }, {
15 | directory: path.join(__dirname, '../test/configs/backstop_data'),
16 | publicPath: '/'
17 | }
18 | ],
19 | client: {
20 | overlay: {
21 | errors: true,
22 | warnings: false,
23 | runtimeErrors: true
24 | }
25 | },
26 | webSocketServer: false
27 | },
28 | output: {
29 | path: path.resolve(__dirname, 'output'),
30 | filename: 'index_bundle.js'
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.?js$/,
36 | exclude: /node_modules/,
37 | use: {
38 | loader: 'babel-loader',
39 | options: {
40 | presets: ['@babel/preset-env', '@babel/preset-react']
41 | }
42 | }
43 | },
44 | {
45 | test: /\.(png|jpg|gif)$/i,
46 | type: 'asset/inline'
47 | }
48 | ]
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/core/command/init.js:
--------------------------------------------------------------------------------
1 | const fs = require('../util/fs');
2 | const logger = require('../util/logger')('init');
3 |
4 | /**
5 | * Copies a boilerplate config file to the current config file location.
6 | */
7 | module.exports = {
8 | execute: function init (config) {
9 | const promises = [];
10 | if (config.engine_scripts) {
11 | logger.log("Copying '" + config.engine_scripts_default + "' to '" + config.engine_scripts + "'");
12 | promises.push(fs.copy(config.engine_scripts_default, config.engine_scripts));
13 | } else {
14 | logger.error('ERROR: Can\'t generate a scripts directory. No \'engine_scripts\' path property was found in backstop.json.');
15 | }
16 |
17 | // Copies a boilerplate config file to the current config file location.
18 | promises.push(fs.copy(config.captureConfigFileNameDefault, config.backstopConfigFileName).then(function () {
19 | logger.log("Configuration file written at '" + config.backstopConfigFileName + "'");
20 | }, function (err) {
21 | throw err;
22 | }));
23 |
24 | return Promise.all(promises);
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/core/command/openReport.js:
--------------------------------------------------------------------------------
1 | const open = require('opn');
2 | const logger = require('../util/logger')('openReport');
3 | const path = require('path');
4 | const http = require('http');
5 | const getRemotePort = require('../util/getRemotePort');
6 | const BACKSTOP_REPORT_SIGNATURE_RE = /BackstopJS Report/i;
7 |
8 | module.exports = {
9 | execute: function (config) {
10 | const port = getRemotePort();
11 | const remoteReportUrl = `http://127.0.0.1:${port}/${config.compareReportURL}?remote`;
12 | return new Promise(function (resolve, reject) {
13 | // would prefer to ping a http://127.0.0.1:${port}/remote with {backstopRemote:ok} response
14 | logger.log('Attempting to ping ', remoteReportUrl);
15 | http.get(remoteReportUrl, (resp) => {
16 | let data = '';
17 | resp.on('data', (chunk) => { data += chunk; });
18 | resp.on('end', () => {
19 | if (BACKSTOP_REPORT_SIGNATURE_RE.test(data)) {
20 | logger.log('Remote found. Opening ' + remoteReportUrl);
21 | resolve(open(remoteReportUrl, { wait: false }));
22 | } else {
23 | logger.log('Remote not detected. Opening ' + config.compareReportURL);
24 | resolve(open(config.compareReportURL, { wait: false }));
25 | }
26 | });
27 | }).on('error', (err) => {
28 | logger.log('Remote not found. Opening ' + config.compareReportURL, 'Error: ' + err.message);
29 | resolve(open(path.resolve(config.compareReportURL), { wait: false }));
30 | });
31 | });
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/core/command/reference.js:
--------------------------------------------------------------------------------
1 | const createBitmaps = require('../util/createBitmaps');
2 | const fs = require('../util/fs');
3 | const logger = require('../util/logger')('clean');
4 | const { shouldRunDocker, runDocker } = require('../util/runDocker');
5 | const engineErrors = require('../util/engineErrors');
6 |
7 | module.exports = {
8 | execute: function (config) {
9 | if (shouldRunDocker(config)) {
10 | return runDocker(config, 'reference');
11 | } else {
12 | let firstStep;
13 | // do not remove reference directory if we are in incremental mode
14 | if (config.args.filter || config.args.i) {
15 | firstStep = Promise.resolve();
16 | } else {
17 | firstStep = fs.remove(config.bitmaps_reference).then(function () {
18 | logger.success(config.bitmaps_reference + ' was cleaned.');
19 | });
20 | }
21 |
22 | return firstStep.then(function () {
23 | return createBitmaps(config, true);
24 | }).then(function () {
25 | console.log('\nRun `$ backstop test` to generate diff report.\n');
26 | return engineErrors(config);
27 | });
28 | }
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/core/command/remote.js:
--------------------------------------------------------------------------------
1 | const logger = require('../util/logger')('remote');
2 | const path = require('path');
3 | const { exec } = require('child_process');
4 | const getRemotePort = require('../util/getRemotePort');
5 | const ssws = require.resolve('super-simple-web-server');
6 |
7 | module.exports = {
8 | execute: function (config) {
9 | const MIDDLEWARE_PATH = path.resolve(config.backstop, 'remote');
10 | const projectPath = path.resolve(config.projectPath);
11 |
12 | return new Promise(function (resolve, reject) {
13 | const port = getRemotePort();
14 | const commandStr = `node ${ssws} ${projectPath} ${MIDDLEWARE_PATH} --config=${config.backstopConfigFileName}`;
15 | const env = { SSWS_HTTP_PORT: port };
16 |
17 | logger.log(`Starting remote with: ${commandStr} with env ${JSON.stringify(env)}`);
18 |
19 | const child = exec(commandStr, { env: { ...env, PATH: process.env.PATH } }, (error) => {
20 | if (error) {
21 | logger.log('Error running backstop remote:', error);
22 | }
23 | });
24 |
25 | child.stdout.on('data', logger.log);
26 |
27 | child.stdout.on('close', data => {
28 | logger.log('Backstop remote connection closed.', data);
29 | resolve(data);
30 | });
31 | });
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/core/command/stop.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const getRemotePort = require('../util/getRemotePort');
3 | const logger = require('../util/logger')('stop');
4 |
5 | module.exports = {
6 | execute: function () {
7 | const port = getRemotePort();
8 | const stopUrl = `http://127.0.0.1:${port}/stop`;
9 | return new Promise((resolve, reject) => {
10 | logger.log('Attempting to ping ', stopUrl);
11 | http.get(stopUrl, (resp) => {
12 | resp.on('end', () => {
13 | logger.log('Stopping backstop remote: success');
14 | process.exit(0);
15 | });
16 | }).on('error', (error) => {
17 | // ECONNRESET is expected if the stop command worked correctly
18 | if (error.code === 'ECONNRESET') {
19 | logger.log('Stopping backstop remote: success');
20 | return process.exit(0);
21 | }
22 | logger.log('Stopping backstop remote: error');
23 | reject(error);
24 | });
25 | });
26 | }
27 | };
28 |
--------------------------------------------------------------------------------
/core/command/test.js:
--------------------------------------------------------------------------------
1 | const createBitmaps = require('../util/createBitmaps');
2 | const { shouldRunDocker, runDocker } = require('../util/runDocker');
3 |
4 | // This task will generate a date-named directory with DOM screenshot files as specified in `./capture/config.json` followed by running a report.
5 | // NOTE: If there is no bitmaps_reference directory or if the bitmaps_reference directory is empty then a new batch of reference files will be generated in the bitmaps_reference directory. Reporting will be skipped in this case.
6 | module.exports = {
7 | execute: function (config) {
8 | const executeCommand = require('./index');
9 | if (shouldRunDocker(config)) {
10 | return runDocker(config, 'test')
11 | .finally(() => {
12 | if (config.openReport && config.report && config.report.indexOf('browser') > -1) {
13 | executeCommand('_openReport', config);
14 | }
15 | });
16 | } else {
17 | return createBitmaps(config, false).then(function () {
18 | return executeCommand('_report', config);
19 | });
20 | }
21 | }
22 | };
23 |
--------------------------------------------------------------------------------
/core/command/version.js:
--------------------------------------------------------------------------------
1 | const version = require('../../package.json').version;
2 |
3 | module.exports = {
4 | execute: function (config) {
5 | return new Promise((resolve, reject) => {
6 | console.log('BackstopJS v' + version);
7 | resolve(version);
8 | });
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/core/util/BackstopException.js:
--------------------------------------------------------------------------------
1 | module.exports = class BackstopException {
2 | constructor (msg, scenario, viewport, originalError) {
3 | this.msg = msg;
4 | this.scenario = scenario;
5 | this.viewport = viewport;
6 | this.originalError = originalError;
7 | }
8 |
9 | toString () {
10 | return 'BackstopException: ' +
11 | this.scenario.label + ' on ' +
12 | this.viewport.label + ': ' +
13 | this.originalError.toString();
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/core/util/Reporter.js:
--------------------------------------------------------------------------------
1 | function Test (pair) {
2 | this.pair = pair;
3 | this.status = 'running';
4 | }
5 |
6 | Test.prototype.passed = function () {
7 | return this.status === 'pass';
8 | };
9 |
10 | function Reporter (testSuite) {
11 | this.testSuite = testSuite;
12 | this.tests = [];
13 | }
14 |
15 | Reporter.prototype.addTest = function (pair) {
16 | const t = new Test(pair);
17 | this.tests.push(t);
18 |
19 | return t;
20 | };
21 |
22 | Reporter.prototype.passed = function () {
23 | return this.tests.filter(test => test.passed()).length;
24 | };
25 |
26 | Reporter.prototype.failed = function () {
27 | return this.tests.filter(test => !test.passed()).length;
28 | };
29 |
30 | Reporter.prototype.getReport = function () {
31 | return {
32 | testSuite: this.testSuite,
33 | tests: this.tests
34 | };
35 | };
36 |
37 | module.exports = Reporter;
38 |
--------------------------------------------------------------------------------
/core/util/allSettled.js:
--------------------------------------------------------------------------------
1 | module.exports = function (promises) {
2 | return Promise.all(promises.map(function (promise) {
3 | return promise.then(function (value) {
4 | return { state: 'fulfilled', value };
5 | }).catch(function (reason) {
6 | return { state: 'rejected', reason };
7 | });
8 | }));
9 | };
10 |
--------------------------------------------------------------------------------
/core/util/compare/compare-hash.js:
--------------------------------------------------------------------------------
1 | const crypto = require('crypto');
2 | const fs = require('fs');
3 |
4 | function getFileHash (filename) {
5 | if (!filename) {
6 | return '';
7 | }
8 | return new Promise(resolve => {
9 | const md5sum = crypto.createHash('md5');
10 | const stream = fs.ReadStream(filename);
11 |
12 | stream.on('data', d => md5sum.update(d));
13 | stream.on('end', () => resolve(md5sum.digest('hex')));
14 | });
15 | }
16 |
17 | module.exports = function (refImage, testImage) {
18 | return Promise.all([getFileHash(refImage), getFileHash(testImage)])
19 | .then(hashes => {
20 | if (hashes[0] !== hashes[1]) {
21 | throw new Error('Images do not match');
22 | }
23 | return {
24 | isSameDimensions: true,
25 | dimensionDifference: { width: 0, height: 0 },
26 | misMatchPercentage: '0.00'
27 | };
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/core/util/compare/compare-resemble.js:
--------------------------------------------------------------------------------
1 | const resemble = require('@mirzazeyrek/node-resemble-js');
2 |
3 | module.exports = function (referencePath, testPath, misMatchThreshold, resembleOutputSettings, requireSameDimensions) {
4 | return new Promise(function (resolve, reject) {
5 | const resembleSettings = resembleOutputSettings || {};
6 | resemble.outputSettings(resembleSettings);
7 | const comparison = resemble(referencePath).compareTo(testPath);
8 |
9 | if (resembleSettings.ignoreAntialiasing) {
10 | comparison.ignoreAntialiasing();
11 | }
12 |
13 | comparison.onComplete(data => {
14 | const misMatchPercentage = resembleSettings.usePreciseMatching ? data.rawMisMatchPercentage : data.misMatchPercentage;
15 | if ((requireSameDimensions === false || data.isSameDimensions === true) && misMatchPercentage <= misMatchThreshold) {
16 | return resolve(data);
17 | }
18 | reject(data);
19 | });
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/core/util/compare/compare.js:
--------------------------------------------------------------------------------
1 | const compareHashes = require('./compare-hash');
2 | const compareResemble = require('./compare-resemble');
3 | const storeFailedDiff = require('./store-failed-diff.js');
4 |
5 | process.on('message', compare);
6 |
7 | function compare (data) {
8 | const { referencePath, testPath, resembleOutputSettings, pair } = data;
9 | const promise = compareHashes(referencePath, testPath)
10 | .catch(() => compareResemble(referencePath, testPath, pair.misMatchThreshold, resembleOutputSettings, pair.requireSameDimensions));
11 | promise
12 | .then(function (data) {
13 | pair.diff = data;
14 | pair.status = 'pass';
15 | return sendMessage(pair);
16 | })
17 | .catch(function (data) {
18 | pair.diff = data;
19 | pair.status = 'fail';
20 |
21 | return storeFailedDiff(testPath, data).then(function (compare) {
22 | pair.diffImage = compare;
23 | return sendMessage(pair);
24 | });
25 | });
26 | }
27 |
28 | function sendMessage (data) {
29 | process.send(data);
30 | }
31 |
--------------------------------------------------------------------------------
/core/util/compare/store-failed-diff-stub.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | // BASE64_PNG_STUB is 1x1 white pixel
5 | const BASE64_PNG_STUB = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=';
6 |
7 | // Utility to ensure `backstop approve` finds a diff image
8 | // call when no reference image exists.
9 | module.exports = function (testPath) {
10 | fs.writeFileSync(getFailedDiffFilename(testPath), BASE64_PNG_STUB, 'base64');
11 | };
12 |
13 | function getFailedDiffFilename (testPath) {
14 | const lastSlash = testPath.lastIndexOf(path.sep);
15 | return testPath.slice(0, lastSlash + 1) + 'failed_diff_' + testPath.slice(lastSlash + 1, testPath.length);
16 | }
17 |
--------------------------------------------------------------------------------
/core/util/compare/store-failed-diff.js:
--------------------------------------------------------------------------------
1 | const streamToPromise = require('./../streamToPromise');
2 | const fs = require('fs');
3 | const path = require('path');
4 |
5 | module.exports = function (testPath, data) {
6 | const failedDiffFilename = getFailedDiffFilename(testPath);
7 | console.log(' See:', failedDiffFilename);
8 |
9 | const failedDiffStream = fs.createWriteStream(failedDiffFilename);
10 | const ext = failedDiffFilename.substring(failedDiffFilename.lastIndexOf('.') + 1);
11 |
12 | if (ext === 'png') {
13 | const storageStream = data.getDiffImage()
14 | .pack()
15 | .pipe(failedDiffStream);
16 | return streamToPromise(storageStream, failedDiffFilename);
17 | }
18 |
19 | if (ext === 'jpg' || ext === 'jpeg') {
20 | fs.writeFileSync(failedDiffFilename, data.getDiffImageAsJPEG(85));
21 | return Promise.resolve(failedDiffFilename);
22 | }
23 | };
24 |
25 | function getFailedDiffFilename (testPath) {
26 | const lastSlash = testPath.lastIndexOf(path.sep);
27 | return testPath.slice(0, lastSlash + 1) + 'failed_diff_' + testPath.slice(lastSlash + 1, testPath.length);
28 | }
29 |
--------------------------------------------------------------------------------
/core/util/engineErrors.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | const compareConfig = require(config.tempCompareConfigFileName).compareConfig;
3 | const error = compareConfig.testPairs.find(testPair => {
4 | return !!testPair.engineErrorMsg;
5 | });
6 |
7 | if (error) {
8 | return Promise.reject(error);
9 | }
10 | return Promise.resolve();
11 | };
12 |
--------------------------------------------------------------------------------
/core/util/ensureDirectoryPath.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | function ensureDirectoryPath (filePath) {
5 | const dirname = path.dirname(filePath);
6 | if (fs.existsSync(dirname)) {
7 | return true;
8 | }
9 | ensureDirectoryPath(dirname);
10 | fs.mkdirSync(dirname);
11 | }
12 |
13 | module.exports = function (path) {
14 | return ensureDirectoryPath(path);
15 | };
16 |
--------------------------------------------------------------------------------
/core/util/findExecutable.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = function (module, bin) {
4 | try {
5 | const pathToExecutableModulePackageJson = require.resolve(path.join(module, 'package.json'));
6 | const executableModulePackageJson = require(pathToExecutableModulePackageJson);
7 | const relativePathToExecutableBinary = executableModulePackageJson.bin[bin] || executableModulePackageJson.bin;
8 | const pathToExecutableModule = pathToExecutableModulePackageJson.replace('package.json', '');
9 | return path.join(pathToExecutableModule, relativePathToExecutableBinary);
10 | } catch (e) {
11 | throw new Error('Couldn\'t find executable for module "' + module + '" and bin "' + bin + '"\n' + e.message);
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/core/util/fs.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const fsExtra = require('fs-extra');
3 | const promisify = require('./promisify');
4 |
5 | const fsPromisified = {
6 | readdir: promisify(fs.readdir),
7 | createWriteStream: fs.createWriteStream,
8 | existsSync: fs.existsSync,
9 | readFile: promisify(fs.readFile),
10 | writeFile: promisify(fs.writeFile),
11 | ensureDir: promisify(fsExtra.ensureDir),
12 | unlink: promisify(fs.unlink),
13 | remove: promisify(fsExtra.remove),
14 | stat: promisify(fs.stat),
15 | copy: promisify(fsExtra.copy),
16 | exists: function exists (file) {
17 | return fsPromisified.stat(file)
18 | .then(function (args) {
19 | return args[0];
20 | })
21 | .catch(function () {
22 | return false;
23 | });
24 | }
25 | };
26 |
27 | module.exports = fsPromisified;
28 |
--------------------------------------------------------------------------------
/core/util/getFreePorts.js:
--------------------------------------------------------------------------------
1 | const portfinder = require('portfinder');
2 | /**
3 | * Gets the free ports.
4 | *
5 | * @param {number} startingPort The starting port
6 | * @param {number} requestedPorts how many ports should we find?
7 | * @return {Array} The free ports.
8 | */
9 | module.exports = function getFreePorts (startingPort, requestedPorts) {
10 | return new Promise((resolve, reject) => {
11 | const R = resolve;
12 | console.log(`searching for ${requestedPorts} available ports.`);
13 | const requestedAmount = requestedPorts;
14 | const freePorts = [];
15 |
16 | function findFreePorts (startPort, pointer) {
17 | const PTR = pointer || 1;
18 | // console.log('freePorts > ', PTR, JSON.stringify(freePorts));
19 | if (PTR > requestedAmount) {
20 | R(freePorts);
21 | return;
22 | }
23 | portfinder.basePort = startPort;
24 | portfinder.getPort(function (err, port) {
25 | if (err) {
26 | reject(new Error(err));
27 | }
28 | freePorts[PTR - 1] = port;
29 | return findFreePorts(port + 1, PTR + 1);
30 | });
31 | }
32 | findFreePorts(startingPort);
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/core/util/getRemotePort.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Gets the custom remote port, otherwise return the default (3000).
3 | *
4 | * @return {number} The remote port.
5 | */
6 | module.exports = function getRemotePort () {
7 | const remotePort = process.env.BACKSTOP_REMOTE_HTTP_PORT || 3000;
8 | return remotePort;
9 | };
10 |
--------------------------------------------------------------------------------
/core/util/isWin.js:
--------------------------------------------------------------------------------
1 | module.exports = /^win/.test(process.platform);
2 |
--------------------------------------------------------------------------------
/core/util/makeSpaces.js:
--------------------------------------------------------------------------------
1 | module.exports = function makeSpaces (length) {
2 | let i = 0;
3 | let result = '';
4 | while (i < length) {
5 | result += ' ';
6 | i++;
7 | }
8 | return result;
9 | };
10 |
--------------------------------------------------------------------------------
/core/util/promisify.js:
--------------------------------------------------------------------------------
1 | module.exports = function promisify (func) {
2 | return function () {
3 | const args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
4 | return new Promise(function (resolve, reject) {
5 | args.push(function (err) {
6 | if (err) {
7 | reject(err);
8 | return;
9 | }
10 |
11 | const args = (arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments));
12 | resolve(args.slice(1));
13 | });
14 | func.apply(this, args);
15 | });
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/core/util/streamToPromise.js:
--------------------------------------------------------------------------------
1 | module.exports = function onStreamEnd (stream, result) {
2 | return new Promise(function (resolve, reject) {
3 | if (stream.writable) {
4 | stream.on('finish', function () {
5 | resolve(result);
6 | });
7 | }
8 |
9 | if (stream.readable) {
10 | stream.on('end', function () {
11 | resolve(result);
12 | });
13 | }
14 |
15 | stream.on('close', function () {
16 | resolve(result);
17 | });
18 |
19 | stream.on('error', function (error) {
20 | reject(error);
21 | });
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | # use bullseye node base, as debian does provide a chromium for arm64 and amd64 flavour
2 | FROM node:20-bullseye
3 |
4 | ARG BACKSTOPJS_VERSION
5 | ARG DEBIAN_FRONTEND=noninteractive
6 |
7 | ENV BACKSTOPJS_VERSION=$BACKSTOPJS_VERSION
8 |
9 | # install chromium and its deps
10 | RUN apt-get -qq update >/dev/null && apt-get install -qq \
11 | fonts-liberation \
12 | # cyrillic
13 | xfonts-cyrillic \
14 | # chinese
15 | xfonts-wqy fonts-wqy-zenhei fonts-arphic-ukai fonts-arphic-uming \
16 | # japanese
17 | fonts-ipafont-mincho fonts-ipafont-gothic fonts-ipafont fonts-vlgothic \
18 | # korean
19 | fonts-unfonts-core fonts-unfonts-extra \
20 | # cjk + emoji font
21 | fonts-noto-cjk fonts-noto-color-emoji \
22 | # chromium
23 | chromium >/dev/null && apt-get -qq clean >/dev/null && rm -rf /var/lib/apt/lists/*
24 |
25 | # skip download, we already have it installed
26 | ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
27 | # playwright shared browser path (does install a 2nd chromium - can't be skipped)
28 | ENV PLAYWRIGHT_BROWSERS_PATH=/opt/pw-browsers
29 | RUN mkdir ${PLAYWRIGHT_BROWSERS_PATH} && npm install -g --unsafe-perm=true --allow-root backstopjs@${BACKSTOPJS_VERSION} && npx --yes --verbose --foreground-scripts playwright install --with-deps
30 |
31 | # set executable path
32 | ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
33 | WORKDIR /src
34 |
35 | ENTRYPOINT ["backstop"]
36 |
--------------------------------------------------------------------------------
/docker/burn-docker-builder.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | echo starting a fire
3 | set -e
4 | set -o errexit
5 |
6 | docker stop `docker ps -qa` > /dev/null 2>&1; ## Stop all running containers
7 | docker buildx stop; ## Stop the buildx builder
8 | docker system prune --all --force --volumes; ## Remove all volumes, images, and containers
9 | docker buildx rm --all-inactive --force; ## Remove all buildx builders
10 | docker buildx prune --all --force; ## Prune buildx builder caches
11 |
12 | echo builders are burned
13 |
--------------------------------------------------------------------------------
/docker/docker-hub-config.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/docker/docker-hub-config.pdf
--------------------------------------------------------------------------------
/docker/docker-repo-config.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/docker/docker-repo-config.png
--------------------------------------------------------------------------------
/docker/hooks/build:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | BACKSTOPJS_VERSION=$(grep -Po '(?<="version": ")[^"]*' $(dirname "$0")/../../package.json)
3 |
4 | docker build --build-arg BACKSTOPJS_VERSION=$BACKSTOPJS_VERSION -t $IMAGE_NAME .
5 |
--------------------------------------------------------------------------------
/docker/hooks/post_push:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | BACKSTOPJS_VERSION=$(grep -Po '(?<="version": ")[^"]*' $(dirname "$0")/../../package.json)
3 |
4 | docker tag $IMAGE_NAME $DOCKER_REPO:$BACKSTOPJS_VERSION
5 | docker push $DOCKER_REPO:$BACKSTOPJS_VERSION
6 |
7 | docker tag $IMAGE_NAME $DOCKER_REPO:latest
8 | docker push $DOCKER_REPO:latest
9 |
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_AgentSecret.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_AgentSecret.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_BackstopJob.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_BackstopJob.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_ConfigNewNode.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_ConfigNewNode.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_General.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_General.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_NewNodeName.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_NewNodeName.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_PostBuildActions_AvoidJUnitReportIssue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_PostBuildActions_AvoidJUnitReportIssue.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_PostBuildActions_DeleteContainer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_PostBuildActions_DeleteContainer.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_PostBuildActions_PublishHTML.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_PostBuildActions_PublishHTML.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_PostBuildActions_PublishJUnit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_PostBuildActions_PublishJUnit.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_Report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_Report.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_SlaveReady.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_SlaveReady.png
--------------------------------------------------------------------------------
/examples/Jenkins/Attachments/Jenkins_SourceControlManagement.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Attachments/Jenkins_SourceControlManagement.png
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "demo",
3 | "viewports": [
4 | {
5 | "label": "PC",
6 | "width": 1920,
7 | "height": 1080
8 | },
9 | {
10 | "label": "iPhone6,6s,7,8",
11 | "width": 375,
12 | "height": 667
13 | }
14 | ],
15 | "onBeforeScript": "puppet/onBefore.js",
16 | "onReadyScript": "puppet/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "demo",
20 | "cookiePath": "",
21 | "url": "https://garris.github.io/BackstopJS/",
22 | "referenceUrl": "",
23 | "readyEvent": "",
24 | "readySelector": "",
25 | "delay": 1000,
26 | "hideSelectors": [],
27 | "removeSelectors": [],
28 | "hoverSelector": "",
29 | "clickSelector": "",
30 | "postInteractionWait": 1000,
31 | "selectors": [],
32 | "selectorExpansion": true,
33 | "misMatchThreshold": 0.1,
34 | "requireSameDimensions": true
35 | }
36 | ],
37 | "paths": {
38 | "bitmaps_reference": "backstop_data/bitmaps_reference",
39 | "bitmaps_test": "backstop_data/bitmaps_test",
40 | "engine_scripts": "backstop_data/engine_scripts",
41 | "html_report": "backstop_data/html_report",
42 | "ci_report": "backstop_data/ci_report"
43 | },
44 | "report": ["CI"],
45 | "engine": "puppeteer",
46 | "engineOptions": {
47 | "ignoreHTTPSErrors": true,
48 | "slowMo": 500,
49 | "args": [
50 | "--no-sandbox",
51 | "--disable-setuid-sandbox",
52 | "--disable-gpu",
53 | "--force-device-scale-factor=1",
54 | "--disable-infobars=true",
55 | "--hide-scrollbars"
56 | ]
57 | },
58 | "asyncCaptureLimit": 2,
59 | "asyncCompareLimit": 20,
60 | "debug": false,
61 | "debugWindow": false
62 | }
63 |
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/bitmaps_reference/demo_demo_0_document_0_PC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Sample/backstop_data/bitmaps_reference/demo_demo_0_document_0_PC.png
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/bitmaps_reference/demo_demo_0_document_1_iPhone66s78.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/Jenkins/Sample/backstop_data/bitmaps_reference/demo_demo_0_document_1_iPhone66s78.png
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/engine_scripts/puppet/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario) => {
2 | const hoverSelector = scenario.hoverSelector;
3 | const clickSelector = scenario.clickSelector;
4 | const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
5 |
6 | if (hoverSelector) {
7 | await page.waitForSelector(hoverSelector);
8 | await page.hover(hoverSelector);
9 | }
10 |
11 | if (clickSelector) {
12 | await page.waitForSelector(clickSelector);
13 | await page.click(clickSelector);
14 | }
15 |
16 | if (postInteractionWait) {
17 | await new Promise(resolve => {
18 | setTimeout(resolve, postInteractionWait);
19 | });
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/engine_scripts/puppet/interceptImages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * INTERCEPT IMAGES
3 | * Listen to all requests. If a request matches IMAGE_URL_RE
4 | * then stub the image with data from IMAGE_STUB_URL
5 | *
6 | * Use this in an onBefore script E.G.
7 | ```
8 | module.exports = async function(page, scenario) {
9 | require('./interceptImages')(page, scenario);
10 | }
11 | ```
12 | *
13 | */
14 |
15 | const fs = require('fs');
16 | const path = require('path');
17 |
18 | const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i;
19 | const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg');
20 | const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL);
21 | const HEADERS_STUB = {};
22 |
23 | module.exports = async function (page, scenario) {
24 | const intercept = async (request, targetUrl) => {
25 | if (IMAGE_URL_RE.test(request.url())) {
26 | await request.respond({
27 | body: IMAGE_DATA_BUFFER,
28 | headers: HEADERS_STUB,
29 | status: 200
30 | });
31 | } else {
32 | request.continue();
33 | }
34 | };
35 | await page.setRequestInterception(true);
36 | page.on('request', intercept);
37 | };
38 |
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/engine_scripts/puppet/loadCookies.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (page, scenario) => {
4 | let cookies = [];
5 | const cookiePath = scenario.cookiePath;
6 |
7 | // READ COOKIES FROM FILE IF EXISTS
8 | if (fs.existsSync(cookiePath)) {
9 | cookies = JSON.parse(fs.readFileSync(cookiePath));
10 | }
11 |
12 | // MUNGE COOKIE DOMAIN
13 | cookies = cookies.map(cookie => {
14 | if (cookie.domain.startsWith('http://') || cookie.domain.startsWith('https://')) {
15 | cookie.url = cookie.domain;
16 | } else {
17 | cookie.url = 'https://' + cookie.domain;
18 | }
19 | delete cookie.domain;
20 | return cookie;
21 | });
22 |
23 | // SET COOKIES
24 | const setCookies = async () => {
25 | return Promise.all(
26 | cookies.map(async (cookie) => {
27 | await page.setCookie(cookie);
28 | })
29 | );
30 | };
31 | await setCookies();
32 | console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
33 | };
34 |
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/engine_scripts/puppet/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | await require('./loadCookies')(page, scenario);
3 |
4 | // Emulate iPhone
5 | if (vp.label == 'iPhone6,6s,7,8') {
6 | await page.setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1');
7 | }
8 |
9 | // Custom Timeout
10 | await page.setDefaultNavigationTimeout(300000);
11 | };
12 |
--------------------------------------------------------------------------------
/examples/Jenkins/Sample/backstop_data/engine_scripts/puppet/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | console.log('SCENARIO > ' + scenario.label);
3 | await require('./clickAndHoverHelper')(page, scenario);
4 |
5 | // add more ready handlers here...
6 | };
7 |
--------------------------------------------------------------------------------
/examples/angularAppWithCssTransitions/backstopConfig_1.json:
--------------------------------------------------------------------------------
1 | {
2 | "viewports": [
3 | {
4 | "name": "phone",
5 | "width": 320,
6 | "height": 480
7 | },
8 | {
9 | "name": "tablet_v",
10 | "width": 568,
11 | "height": 1024
12 | },
13 | {
14 | "name": "tablet_h",
15 | "width": 1024,
16 | "height": 768
17 | }
18 | ],
19 | "scenarios": [
20 | {
21 | "label": "measure twice",
22 | "url": "test/simple.html",
23 | "hideSelectors": [
24 | ],
25 | "removeSelectors": [
26 | ],
27 | "selectors": [
28 | ".page-header h1",
29 | ".page-header small",
30 | ".graph:nth-of-type(1)",
31 | ".graph:nth-of-type(2)",
32 | "body",
33 | "nav"
34 | ],
35 | "readyEvent": "backstop.ready",
36 | "delay": 10,
37 | "misMatchThreshold" : 0.1
38 | }
39 | ],
40 | "paths": {
41 | "bitmaps_reference": "../../backstop_data/bitmaps_reference",
42 | "bitmaps_test": "../../backstop_data/bitmaps_test",
43 | "compare_data": "../../backstop_data/bitmaps_test/compare.json"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/examples/angularAppWithCssTransitions/backstopConfig_2.json:
--------------------------------------------------------------------------------
1 | {
2 | "viewports": [
3 | {
4 | "name": "phone",
5 | "width": 320,
6 | "height": 480
7 | },
8 | {
9 | "name": "tablet_h",
10 | "width": 1024,
11 | "height": 768
12 | }
13 | ],
14 | "scenarios": [
15 | {
16 | "label": "measure twice",
17 | "url": "test/simple.html#testHash",
18 | "hideSelectors": [
19 | ],
20 | "removeSelectors": [
21 | ],
22 | "selectors": [
23 | "body"
24 | ],
25 | "readyEvent": "backstop.ready",
26 | "delay": 10,
27 | "misMatchThreshold" : 0.1
28 | }
29 | ],
30 | "paths": {
31 | "bitmaps_reference": "../../backstop_data/bitmaps_reference",
32 | "bitmaps_test": "../../backstop_data/bitmaps_test",
33 | "compare_data": "../../backstop_data/bitmaps_test/compare.json"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/featureTests/dist/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/featureTests/dist/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/examples/featureTests/dist/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/featureTests/dist/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/examples/featureTests/dist/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/featureTests/dist/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/examples/featureTests/dist/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/examples/featureTests/readme.md:
--------------------------------------------------------------------------------
1 |
2 | ### My Cool Project
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/examples/jsBasedConfig/backstopConfig.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | id: 'test',
3 | viewports: [
4 | {
5 | name: 'phone',
6 | width: 320,
7 | height: 480
8 | },
9 | {
10 | name: 'tablet_v',
11 | width: 568,
12 | height: 1024
13 | },
14 | {
15 | name: 'tablet_h',
16 | width: 1024,
17 | height: 768
18 | }
19 | ],
20 | scenarios: [
21 | {
22 | label: 'My Homepage',
23 | url: 'https://garris.github.io/BackstopJS/',
24 | hideSelectors: [],
25 | removeSelectors: [],
26 | selectorExpansion: true,
27 | selectors: [
28 | 'body',
29 | '.jumbotron',
30 | '.firstPanel > div',
31 | '.secondPanel'
32 | ],
33 | readyEvent: null,
34 | delay: 500,
35 | misMatchThreshold: 0.1,
36 | onBeforeScript: 'onBefore.js',
37 | onReadyScript: 'onReady.js'
38 | }
39 | ],
40 | paths: {
41 | bitmaps_reference: 'backstop_data/bitmaps_reference',
42 | bitmaps_test: 'backstop_data/bitmaps_test',
43 | html_report: 'backstop_data/html_report',
44 | ci_report: 'backstop_data/ci_report'
45 | },
46 | report: ['browser'],
47 | debug: false
48 | };
49 |
--------------------------------------------------------------------------------
/examples/jsBasedConfig/readme.md:
--------------------------------------------------------------------------------
1 | BackstopJS allows you to import all config parameters as a node module (as an option instead of JSON) which allows you to use comments, variables and logic etc. inside of your config.
2 |
3 | To use a js module based config file, explicitly specify your config filepath when running a command. e.g.
4 |
5 | ```sh
6 | $ backstop test --config=backstopTests/backstopConfig
7 | ```
8 | _1. You don't actually need to specify a file extension as part of your config parameter -- but you can add one if you like._
9 |
10 | _2. See the the main readme for more info on setting the config file path._
11 |
--------------------------------------------------------------------------------
/examples/myCoolProject/dist/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/myCoolProject/dist/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/examples/myCoolProject/dist/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/myCoolProject/dist/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/examples/myCoolProject/dist/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/myCoolProject/dist/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/examples/myCoolProject/dist/js/npm.js:
--------------------------------------------------------------------------------
1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment.
2 | require('../../js/transition.js')
3 | require('../../js/alert.js')
4 | require('../../js/button.js')
5 | require('../../js/carousel.js')
6 | require('../../js/collapse.js')
7 | require('../../js/dropdown.js')
8 | require('../../js/modal.js')
9 | require('../../js/tooltip.js')
10 | require('../../js/popover.js')
11 | require('../../js/scrollspy.js')
12 | require('../../js/tab.js')
13 | require('../../js/affix.js')
--------------------------------------------------------------------------------
/examples/myCoolProject/readme.md:
--------------------------------------------------------------------------------
1 |
2 | ### My Cool Project
3 |
4 | do `backstop init` then replace scenario with...
5 |
6 | ```
7 | {
8 | "label": "My Homepage",
9 | "url": "index.html",
10 | "hideSelectors": [],
11 | "removeSelectors": [],
12 | "selectors": [
13 | "nav",
14 | ".jumbotron",
15 | "body .col-md-4:nth-of-type(1)",
16 | "body .col-md-4:nth-of-type(2)",
17 | "body .col-md-4:nth-of-type(3)",
18 | "footer"
19 | ],
20 | "readyEvent": null,
21 | "delay": 500,
22 | "misMatchThreshold" : 0.1,
23 | "onBeforeScript": "onBefore.js",
24 | "onReadyScript": "onReady.js"
25 | }
26 |
27 | ```
28 |
29 |
30 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/backstop.config.js:
--------------------------------------------------------------------------------
1 | module.exports = options => {
2 | return {
3 | id: `${options.project}_test`,
4 | viewports: [
5 | {
6 | name: 'tablet',
7 | width: 1024,
8 | height: 768
9 | }
10 | ],
11 | scenarios: options.scenarios,
12 | paths: {
13 | bitmaps_reference: `backstop_data/${options.project}/bitmaps_reference`,
14 | bitmaps_test: `backstop_data/${options.project}/bitmaps_test`,
15 | html_report: `backstop_data/${options.project}/html_report`,
16 | ci_report: `backstop_data/${options.project}/ci_report`
17 | },
18 | report: ['browser', 'CI'],
19 | debug: false
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-integration",
3 | "version": "1.0.0",
4 | "description": "A simple example of using BackstopJS with NodeJS and passing the config some parameters",
5 | "main": "backstop.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [
10 | "backstopjs",
11 | "regression testing"
12 | ],
13 | "author": "Alex Bondarev",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "backstopjs": "^2.3.7",
17 | "koa": "^1.2.4",
18 | "koa-serve": "^0.1.7",
19 | "lodash": "^4.17.4",
20 | "yargs": "^6.6.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/first-project/about-us.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | First project - About Us
9 |
10 |
11 | My first project
12 | About us
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias aliquid, aspernatur assumenda autem deserunt dignissimos eligendi et expedita impedit laudantium maxime modi nemo neque obcaecati quis quo soluta sunt voluptatem.
14 |
15 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/first-project/homepage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | First project - homepage
9 |
10 |
11 | My first project
12 | Homepage
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias aliquid, aspernatur assumenda autem deserunt dignissimos eligendi et expedita impedit laudantium maxime modi nemo neque obcaecati quis quo soluta sunt voluptatem.
14 | Heading
15 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt, labore, voluptate! Consequatur dolores ducimus error est et fugit ipsam, odit omnis placeat quam sit temporibus ut. Commodi dolores eos possimus?
16 |
17 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/first-project/ignore-me.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | This page should not be tested
9 |
10 |
11 | Do not test me
12 |
13 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/second-project/contact-us.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Second project - Contact Us
9 |
10 |
11 | My second project
12 | Contact us
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias aliquid, aspernatur assumenda autem deserunt dignissimos eligendi et expedita impedit laudantium maxime modi nemo neque obcaecati quis quo soluta sunt voluptatem.
14 |
15 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/second-project/dummy.json:
--------------------------------------------------------------------------------
1 | {
2 | "foo": "bar"
3 | }
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/second-project/ignore-me.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | This page should not be tested
9 |
10 |
11 | Do not test me
12 |
13 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/second-project/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Second project - index page
9 |
10 |
11 | My second project
12 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias aliquid, aspernatur assumenda autem deserunt dignissimos eligendi et expedita impedit laudantium maxime modi nemo neque obcaecati quis quo soluta sunt voluptatem.
13 | Heading
14 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt, labore, voluptate! Consequatur dolores ducimus error est et fugit ipsam, odit omnis placeat quam sit temporibus ut. Commodi dolores eos possimus?
15 |
16 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/third-project/ignore-me.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | This page should not be tested
9 |
10 |
11 | Do not test me
12 |
13 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/public/third-project/terms-of-use.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Third project - Terms Of Use page
9 |
10 |
11 | My third project
12 | Terms of use
13 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias aliquid, aspernatur assumenda autem deserunt dignissimos eligendi et expedita impedit laudantium maxime modi nemo neque obcaecati quis quo soluta sunt voluptatem.
14 | Heading
15 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt, labore, voluptate! Consequatur dolores ducimus error est et fugit ipsam, odit omnis placeat quam sit temporibus ut. Commodi dolores eos possimus?
16 |
17 |
--------------------------------------------------------------------------------
/examples/nodeIntegration/server.js:
--------------------------------------------------------------------------------
1 | const koa = require('koa');
2 | const serve = require('koa-serve');
3 |
4 | const app = koa();
5 |
6 | app.use(serve('public'));
7 |
8 | app.listen(8000, () => {
9 | console.log('Koa is listening at localhost:8000');
10 | });
11 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "responsiveDemoPage",
3 | "viewports": [
4 | {
5 | "label": "phone",
6 | "width": 320,
7 | "height": 480
8 | },
9 | {
10 | "label": "tablet",
11 | "width": 1024,
12 | "height": 768
13 | }
14 | ],
15 | "onBeforeScript": "puppet/onBefore.js",
16 | "onReadyScript": "puppet/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "comparePage",
20 | "cookiePath": "backstop_data/engine_scripts/cookies.json",
21 | "url": "http://127.0.0.1:3000/",
22 | "referenceUrl": "",
23 | "readyEvent": "",
24 | "readySelector": "",
25 | "delay": 100,
26 | "hideSelectors": [],
27 | "removeSelectors": [],
28 | "hoverSelector": "",
29 | "clickSelector": "",
30 | "postInteractionWait": 0,
31 | "selectors": [],
32 | "selectorExpansion": true,
33 | "expect": 0,
34 | "misMatchThreshold" : 0.1,
35 | "requireSameDimensions": true
36 | }
37 | ],
38 | "paths": {
39 | "bitmaps_reference": "backstop_data/bitmaps_reference",
40 | "bitmaps_test": "backstop_data/bitmaps_test",
41 | "engine_scripts": "backstop_data/engine_scripts",
42 | "html_report": "backstop_data/html_report",
43 | "ci_report": "backstop_data/ci_report"
44 | },
45 | "report": ["browser"],
46 | "engine": "puppeteer",
47 | "engineOptions": {
48 | "args": ["--no-sandbox"]
49 | },
50 | "asyncCaptureLimit": 5,
51 | "asyncCompareLimit": 50,
52 | "debug": false,
53 | "debugWindow": false
54 | }
55 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_0_phone.png
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_1_tablet.png
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/bitmaps_reference/responsiveDemoPage_comparePage_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/backstop_data/bitmaps_reference/responsiveDemoPage_comparePage_0_document_0_phone.png
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/bitmaps_reference/responsiveDemoPage_comparePage_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/backstop_data/bitmaps_reference/responsiveDemoPage_comparePage_0_document_1_tablet.png
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": ".www.yourdomain.com",
4 | "path": "/",
5 | "name": "yourCookieName",
6 | "value": "yourCookieValue",
7 | "expirationDate": 1798790400,
8 | "hostOnly": false,
9 | "httpOnly": false,
10 | "secure": false,
11 | "session": false,
12 | "sameSite": "no_restriction"
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/imageStub.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/backstop_data/engine_scripts/imageStub.jpg
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/puppet/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario) => {
2 | const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3 | const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4 | const scrollToSelector = scenario.scrollToSelector;
5 | const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
6 |
7 | if (hoverSelector) {
8 | for (const hoverSelectorIndex of [].concat(hoverSelector)) {
9 | await page.waitForSelector(hoverSelectorIndex);
10 | await page.hover(hoverSelectorIndex);
11 | }
12 | }
13 |
14 | if (clickSelector) {
15 | for (const clickSelectorIndex of [].concat(clickSelector)) {
16 | await page.waitForSelector(clickSelectorIndex);
17 | await page.click(clickSelectorIndex);
18 | }
19 | }
20 |
21 | if (postInteractionWait) {
22 | await new Promise(resolve => {
23 | setTimeout(resolve, postInteractionWait);
24 | });
25 | }
26 |
27 | if (scrollToSelector) {
28 | await page.waitForSelector(scrollToSelector);
29 | await page.evaluate(scrollToSelector => {
30 | document.querySelector(scrollToSelector).scrollIntoView();
31 | }, scrollToSelector);
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/puppet/interceptImages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * INTERCEPT IMAGES
3 | * Listen to all requests. If a request matches IMAGE_URL_RE
4 | * then stub the image with data from IMAGE_STUB_URL
5 | *
6 | * Use this in an onBefore script E.G.
7 | ```
8 | module.exports = async function(page, scenario) {
9 | require('./interceptImages')(page, scenario);
10 | }
11 | ```
12 | *
13 | */
14 |
15 | const fs = require('fs');
16 | const path = require('path');
17 |
18 | const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i;
19 | const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg');
20 | const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL);
21 | const HEADERS_STUB = {};
22 |
23 | module.exports = async function (page, scenario) {
24 | const intercept = async (request, targetUrl) => {
25 | if (IMAGE_URL_RE.test(request.url())) {
26 | await request.respond({
27 | body: IMAGE_DATA_BUFFER,
28 | headers: HEADERS_STUB,
29 | status: 200
30 | });
31 | } else {
32 | request.continue();
33 | }
34 | };
35 | await page.setRequestInterception(true);
36 | page.on('request', intercept);
37 | };
38 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/puppet/loadCookies.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (page, scenario) => {
4 | let cookies = [];
5 | const cookiePath = scenario.cookiePath;
6 |
7 | // READ COOKIES FROM FILE IF EXISTS
8 | if (fs.existsSync(cookiePath)) {
9 | cookies = JSON.parse(fs.readFileSync(cookiePath));
10 | }
11 |
12 | // MUNGE COOKIE DOMAIN
13 | cookies = cookies.map(cookie => {
14 | if (cookie.domain.startsWith('http://') || cookie.domain.startsWith('https://')) {
15 | cookie.url = cookie.domain;
16 | } else {
17 | cookie.url = 'https://' + cookie.domain;
18 | }
19 | delete cookie.domain;
20 | return cookie;
21 | });
22 |
23 | // SET COOKIES
24 | const setCookies = async () => {
25 | return Promise.all(
26 | cookies.map(async (cookie) => {
27 | await page.setCookie(cookie);
28 | })
29 | );
30 | };
31 | await setCookies();
32 | console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
33 | };
34 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/puppet/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | await require('./loadCookies')(page, scenario);
3 | };
4 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/backstop_data/engine_scripts/puppet/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | console.log('SCENARIO > ' + scenario.label);
3 | await require('./clickAndHoverHelper')(page, scenario);
4 |
5 | // add more ready handlers here...
6 | };
7 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/do_not_razz_the_lemur.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/do_not_razz_the_lemur.png
--------------------------------------------------------------------------------
/examples/responsiveDemo/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/responsiveDemo/favicon.ico
--------------------------------------------------------------------------------
/examples/responsiveDemo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "responsivedemo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.html",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "serve": "node ./node_modules/super-simple-web-server/ ./",
9 | "reference": "node ../../cli/index.js reference"
10 | },
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "super-simple-web-server": "^1.0.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/examples/responsiveDemo/readme.md:
--------------------------------------------------------------------------------
1 | Start me with...
2 |
3 | ```
4 | npm run serve
5 | ```
6 |
7 | ...then you can see me here...
8 |
9 | [http://127.0.0.1:3000]()
10 |
11 | Keep on truckin!
--------------------------------------------------------------------------------
/examples/responsiveDemo/responsiveDemo.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 14px;
3 | }
4 | @media (min-width: 768px) {
5 | html {
6 | font-size: 16px;
7 | }
8 | }
9 |
10 | .container {
11 | max-width: 960px;
12 | }
13 |
14 | .pricing-header {
15 | max-width: 700px;
16 | }
17 |
18 | .card-deck .card {
19 | min-width: 220px;
20 | }
21 |
22 | .border-top { border-top: 1px solid #e5e5e5; }
23 | .border-bottom { border-bottom: 1px solid #e5e5e5; }
24 |
25 | .box-shadow { box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); }
26 |
27 | code { background-color: #eeeeee; }
28 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/backstop.json:
--------------------------------------------------------------------------------
1 | {
2 | "viewports": [
3 | {
4 | "name": "phone",
5 | "width": 320,
6 | "height": 480
7 | },
8 | {
9 | "name": "tablet_v",
10 | "width": 568,
11 | "height": 1024
12 | },
13 | {
14 | "name": "tablet_h",
15 | "width": 1024,
16 | "height": 768
17 | }
18 | ],
19 | "scenarios": [
20 | {
21 | "label": "My Homepage",
22 | "url": "index.html",
23 | "hideSelectors": [],
24 | "removeSelectors": [],
25 | "selectors": [
26 | "#main"
27 | ],
28 | "readyEvent": "gmapResponded",
29 | "delay": 100,
30 | "misMatchThreshold" : 0,
31 | "onBeforeScript": "onBefore.js",
32 | "onReadyScript": "onReady.js"
33 | }
34 | ],
35 | "paths": {
36 | "bitmaps_reference": "backstop_data/bitmaps_reference",
37 | "bitmaps_test": "backstop_data/bitmaps_test",
38 | "html_report": "backstop_data/html_report",
39 | "ci_report": "backstop_data/ci_report"
40 | },
41 | "report": ["browser"],
42 | "resembleOutputOptions": {
43 | "ignoreAntialiasing": true,
44 | "usePreciseMatching": true
45 | },
46 | "debug": false
47 | }
48 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/backstop_data/bitmaps_reference/My Homepage_0_main_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/simpleReactApp/backstop_data/bitmaps_reference/My Homepage_0_main_0_phone.png
--------------------------------------------------------------------------------
/examples/simpleReactApp/backstop_data/bitmaps_reference/My Homepage_0_main_1_tablet_v.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/simpleReactApp/backstop_data/bitmaps_reference/My Homepage_0_main_1_tablet_v.png
--------------------------------------------------------------------------------
/examples/simpleReactApp/backstop_data/bitmaps_reference/My Homepage_0_main_2_tablet_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/examples/simpleReactApp/backstop_data/bitmaps_reference/My Homepage_0_main_2_tablet_h.png
--------------------------------------------------------------------------------
/examples/simpleReactApp/components/CurrentLocation.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 |
3 | const CurrentLocation = React.createClass({
4 |
5 | toggleFavorite() {
6 | this.props.onFavoriteToggle(this.props.address);
7 | },
8 |
9 | render() {
10 | let starClassName = 'glyphicon glyphicon-star-empty';
11 |
12 | if (this.props.favorite) {
13 | starClassName = 'glyphicon glyphicon-star';
14 | }
15 |
16 | return (
17 |
18 |
{this.props.address}
19 |
20 |
21 | );
22 | }
23 |
24 | });
25 |
26 | module.exports = CurrentLocation;
27 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/components/LocationItem.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const moment = require('moment');
3 |
4 | const LocationItem = React.createClass({
5 |
6 | handleClick () {
7 | this.props.onClick(this.props.address);
8 | },
9 |
10 | render () {
11 | let cn = 'list-group-item';
12 |
13 | if (this.props.active) {
14 | cn += ' active-location';
15 | }
16 |
17 | return (
18 |
19 | {this.props.address}
20 | { moment(this.props.timestamp).fromNow() }
21 |
22 |
23 | );
24 | }
25 |
26 | });
27 |
28 | module.exports = LocationItem;
29 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/components/LocationList.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const LocationItem = require('./LocationItem');
3 |
4 | const LocationList = React.createClass({
5 |
6 | render() {
7 | const self = this;
8 |
9 | const locations = this.props.locations.map(function (l) {
10 | const active = self.props.activeLocationAddress == l.address;
11 |
12 | // Notice that we are passing the onClick callback of this
13 | // LocationList to each LocationItem.
14 |
15 | return ;
17 | });
18 |
19 | if (!locations.length) {
20 | return null;
21 | }
22 |
23 | return (
24 |
25 | Saved Locations
26 | {locations}
27 |
28 | );
29 | }
30 |
31 | });
32 |
33 | module.exports = LocationList;
34 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/components/Map.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 |
3 | const Map = React.createClass({
4 |
5 | componentDidMount() {
6 | // Only componentDidMount is called when the component is first added to
7 | // the page. This is why we are calling the following method manually.
8 | // This makes sure that our map initialization code is run the first time.
9 |
10 | this.componentDidUpdate();
11 | },
12 |
13 | componentDidUpdate() {
14 | if (this.lastLat == this.props.lat && this.lastLng == this.props.lng) {
15 | // The map has already been initialized at this address.
16 | // Return from this method so that we don't reinitialize it
17 | // (and cause it to flicker).
18 |
19 | return;
20 | }
21 |
22 | this.lastLat = this.props.lat;
23 | this.lastLng = this.props.lng;
24 |
25 | const map = new GMaps({
26 | el: '#map',
27 | lat: this.props.lat,
28 | lng: this.props.lng
29 | });
30 |
31 | // Adding a marker to the location we are showing
32 |
33 | map.addMarker({
34 | lat: this.props.lat,
35 | lng: this.props.lng
36 | });
37 | },
38 |
39 | render() {
40 | return (
41 |
42 |
Loading...
43 |
44 |
45 | );
46 | }
47 |
48 | });
49 |
50 | module.exports = Map;
51 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/components/Search.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 |
3 | const Search = React.createClass({
4 |
5 | getInitialState() {
6 | return {value: ''};
7 | },
8 |
9 | handleChange(event) {
10 | this.setState({value: event.target.value});
11 | },
12 |
13 | handleSubmit(event) {
14 | event.preventDefault();
15 |
16 | // When the form is submitted, call the onSearch callback that is passed to the component
17 |
18 | this.props.onSearch(this.state.value);
19 |
20 | // Unfocus the text input field
21 | this.getDOMNode().querySelector('input').blur();
22 | },
23 |
24 | render() {
25 | return (
26 |
39 | );
40 | }
41 | });
42 |
43 | module.exports = Search;
44 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Your First Webapp With React
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/main.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const App = require('./components/App');
3 |
4 | React.render(
5 | ,
6 | document.getElementById('main')
7 | );
8 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "first-webapp-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "main.js",
6 | "private": true,
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "dev-backstop-test": "node ../../cli/index.js test",
10 | "watch": "watchify -v -d -t [ reactify --es6 ] main.js -o compiled.js",
11 | "build": "NODE_ENV=production browserify -t [ reactify --es6 ] main.js | uglifyjs > compiled.js && backstop test"
12 | },
13 | "author": "Tutorialzine",
14 | "license": "MIT",
15 | "dependencies": {
16 | "moment": "^2.10.2",
17 | "react": "^0.14.0"
18 | },
19 | "devDependencies": {
20 | "browserify": "^9.0.8",
21 | "reactify": "^1.1.0",
22 | "uglify-js": "^2.4.20",
23 | "watchify": "^3.1.2"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/examples/simpleReactApp/readme.md:
--------------------------------------------------------------------------------
1 | **This simple project was originally found here...**
2 |
3 | http://tutorialzine.com/2015/04/first-webapp-react/
4 |
5 | Install:
6 | make sure you are in the `simpleReactApp` directory and then...
7 | `npm install`
8 |
9 | then Build...
10 | `npm run build`
11 |
12 | Then open index.html in your browser.
13 |
14 | **Note:** ignore antialiasing is used here.
15 | See backstop.json: `"resembleOutputOptions": {"ignoreAntialiasing": true}`
16 |
17 | ---
18 |
19 | **A BackstopJS test configuration file has already been added to this project.**
20 |
21 | To test, simply run the build command above, and Backstop will open the html report in `./backstop_data/html_report`.
22 |
23 | You can also run `npm run dev-backstop-test`, if you have run `npm install` at the root directory of the BackstopJS directory.
24 |
--------------------------------------------------------------------------------
/old_splash_page_v2.0/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/favicon.ico
--------------------------------------------------------------------------------
/old_splash_page_v2.0/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/old_splash_page_v2.0/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/old_splash_page_v2.0/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/CLI_report.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/CLI_report.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/backgrounds/cream_dust/cream_dust.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/backgrounds/cream_dust/cream_dust.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/backgrounds/cream_dust/cream_dust_@2X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/backgrounds/cream_dust/cream_dust_@2X.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/backgrounds/cream_dust/readme.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | ========================================================
4 | This pattern is downloaded from www.subtlepatterns.com
5 | If you need more, that's where to get'em.
6 | ========================================================
7 |
8 |
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/backgrounds/subtlenet2/readme.txt:
--------------------------------------------------------------------------------
1 |
2 |
3 | ========================================================
4 | This pattern is downloaded from www.subtlepatterns.com
5 | If you need more, that's where to get'em.
6 | ========================================================
7 |
8 |
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/backgrounds/subtlenet2/subtlenet2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/backgrounds/subtlenet2/subtlenet2.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/backgrounds/subtlenet2/subtlenet2_@2X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/backgrounds/subtlenet2/subtlenet2_@2X.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/browserReport.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/browserReport.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/one.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/one@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/one@2x.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/three.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/three.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/three@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/three@2x.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/two.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/img/two@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/old_splash_page_v2.0/img/two@2x.png
--------------------------------------------------------------------------------
/old_splash_page_v2.0/js/main.js:
--------------------------------------------------------------------------------
1 | $('button').click(function () {
2 | window.location.href = 'https://github.com/garris/BackstopJS';
3 | });
4 |
5 | // TODO: modify this for v2.0 content vvvvv
6 |
7 | const READY_LAG_MS = 10000;
8 | const READY_TAG = '_the_lemur_is_ready_to_see_you';
9 | const LEMUR_CLASS_ACTION = 'hideLemur';
10 | const COOKIE_TEST = /cookie/i;
11 | const CLICK_TEST = /click/i;
12 | const DELAY_TEST = /delay/i;
13 |
14 | if (COOKIE_TEST.test(window.location.search)) {
15 | showCookies();
16 | }
17 | if (CLICK_TEST.test(window.location.search)) {
18 | modifyLemurBehavior();
19 | }
20 | if (DELAY_TEST.test(window.location.search)) {
21 | delayLemurification();
22 | }
23 |
24 | function modifyLemurBehavior () {
25 | document.body.addEventListener('click', evt => {
26 | if (evt.target.id === 'theLemur') {
27 | containTheLemur();
28 | evt.preventDefault();
29 | } else {
30 | releaseTheLemur();
31 | }
32 | });
33 | console.log('lemur behavior is modified');
34 | }
35 |
36 | function showCookies () {
37 | document.getElementsByClassName('logoBlock')[0].innerText = 'cookies > ' + document.cookie;
38 | }
39 |
40 | function setReadyFlags () {
41 | console.log(READY_TAG);
42 | document.body.classList.add(READY_TAG);
43 | }
44 |
45 | function releaseTheLemur () {
46 | document.body.classList.remove(LEMUR_CLASS_ACTION);
47 | }
48 |
49 | function containTheLemur () {
50 | document.body.classList.add(LEMUR_CLASS_ACTION);
51 | }
52 |
53 | function delayLemurification () {
54 | containTheLemur();
55 | setTimeout(releaseTheLemur, READY_LAG_MS);
56 | setTimeout(setReadyFlags, READY_LAG_MS + 1000);
57 | }
58 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.eslintrc",
3 | "env": {
4 | "mocha": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/test/cli/index_spec.js:
--------------------------------------------------------------------------------
1 | const mockery = require('mockery');
2 | const assert = require('assert');
3 | const sinon = require('sinon');
4 |
5 | describe('cli', function () {
6 | beforeEach(function () {
7 | mockery.enable({ warnOnUnregistered: false, useCleanCache: true });
8 | });
9 |
10 | afterEach(function () {
11 | mockery.deregisterAll();
12 | mockery.disable();
13 | });
14 |
15 | it('should call the runner without custom options correctly', function (done) {
16 | process.argv = ['node', 'backstop', 'test'];
17 | const promiseMock = Promise.resolve();
18 | const runnerMock = sinon.stub().returns(promiseMock);
19 | mockery.registerMock('../core/runner', runnerMock);
20 |
21 | require('../../cli/index');
22 |
23 | promiseMock.then(() => {
24 | assert.strictEqual(process.exitCode, undefined);
25 | assert(runnerMock.calledWith('test'));
26 | done();
27 | });
28 | });
29 |
30 | it('should exit with code 1 if runner fails', function (done) {
31 | process.argv = ['node', 'backstop', 'test'];
32 | const promiseMock = Promise.reject(new Error('errorMock'));
33 | const runnerMock = sinon.stub().returns(promiseMock);
34 | mockery.registerMock('../core/runner', runnerMock);
35 |
36 | require('../../cli/index');
37 |
38 | promiseMock.catch(() => {
39 | assert.strictEqual(process.exitCode, 1);
40 | done();
41 | });
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/test/cli/usage_spec.js:
--------------------------------------------------------------------------------
1 | const usage = require('../../cli/usage');
2 | const assert = require('assert');
3 |
4 | const expectedUsage = /Welcome to BackstopJS/;
5 |
6 | describe('the cli usage', function () {
7 | it('should print usage hints correctly', function () {
8 | assert(expectedUsage.test(usage));
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/test/configs/backstop.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "backstop_default",
3 | "viewports": [
4 | {
5 | "label": "phone",
6 | "width": 320,
7 | "height": 480
8 | },
9 | {
10 | "label": "tablet",
11 | "width": 1024,
12 | "height": 768
13 | }
14 | ],
15 | "onBeforeScript": "puppet/onBefore.js",
16 | "onReadyScript": "puppet/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "BackstopJS Homepage",
20 | "cookiePath": "backstop_data/engine_scripts/cookies.json",
21 | "url": "https://garris.github.io/BackstopJS/",
22 | "referenceUrl": "",
23 | "readyEvent": "",
24 | "readySelector": "",
25 | "delay": 0,
26 | "hideSelectors": [],
27 | "removeSelectors": [],
28 | "hoverSelector": "",
29 | "clickSelector": "",
30 | "postInteractionWait": 0,
31 | "selectors": [],
32 | "selectorExpansion": true,
33 | "misMatchThreshold" : 0.1,
34 | "requireSameDimensions": true
35 | }
36 | ],
37 | "paths": {
38 | "bitmaps_reference": "backstop_data/bitmaps_reference",
39 | "bitmaps_test": "backstop_data/bitmaps_test",
40 | "engine_scripts": "backstop_data/engine_scripts",
41 | "html_report": "backstop_data/html_report",
42 | "ci_report": "backstop_data/ci_report"
43 | },
44 | "report": ["browser"],
45 | "engine": "puppet",
46 | "engineOptions": {
47 | "args": ["--no-sandbox"]
48 | },
49 | "asyncCaptureLimit": 5,
50 | "asyncCompareLimit": 50,
51 | "debug": false,
52 | "debugWindow": false,
53 | "archiveReport": true,
54 | "scenarioLogsInReports": true
55 | }
56 |
--------------------------------------------------------------------------------
/test/configs/backstop_alt.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | id: 'puppet_backstop_features',
3 | viewports: [
4 | {
5 | label: 'phone',
6 | width: 320,
7 | height: 480
8 | },
9 | {
10 | label: 'tablet',
11 | width: 1024,
12 | height: 768
13 | }
14 | ],
15 | onBeforeScript: 'puppet/onBefore.js',
16 | onReadyScript: 'puppet/onReady.js',
17 | scenarios: [
18 | {
19 | label: 'Simple',
20 | url: 'https://garris.github.io/BackstopJS/'
21 | }
22 | ],
23 | paths: {
24 | bitmaps_reference: 'backstop_data/bitmaps_reference',
25 | bitmaps_test: 'backstop_data/bitmaps_test',
26 | engine_scripts: 'backstop_data/engine_scripts',
27 | html_report: 'backstop_data/html_report',
28 | ci_report: 'backstop_data/ci_report'
29 | },
30 | report: ['browser'],
31 | engine: 'puppet',
32 | engineOptions: {
33 | args: ['--no-sandbox']
34 | },
35 | asyncCaptureLimit: 10,
36 | asyncCompareLimit: 50,
37 | debug: false,
38 | debugWindow: false
39 | };
40 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/backstop_default_BackstopJS_Homepage_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/backstop_playwright_BackstopJS_Homepage_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/backstop_playwright_BackstopJS_Homepage_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/backstop_playwright_BackstopJS_Homepage_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/backstop_playwright_BackstopJS_Homepage_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_Simple_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_Simple_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_Simple_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_Simple_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_click_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_click_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_click_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_click_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_cookies_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_cookies_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_cookies_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_cookies_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_delay_0_getItBlocknth-child3_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_delay_0_getItBlocknth-child3_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_delay_0_getItBlocknth-child3_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_delay_0_getItBlocknth-child3_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_0_getItBlock_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_0_getItBlock_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_0_getItBlock_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_0_getItBlock_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_1_getItBlock__n1_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_1_getItBlock__n1_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_1_getItBlock__n1_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_1_getItBlock__n1_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_2_getItBlock__n2_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_2_getItBlock__n2_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_2_getItBlock__n2_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_2_getItBlock__n2_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_3_getItBlock__n3_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_3_getItBlock__n3_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_3_getItBlock__n3_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expanded_3_getItBlock__n3_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_0_getItBlock_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_0_getItBlock_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_0_getItBlock_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_0_getItBlock_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_1_getItBlock__n1_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_1_getItBlock__n1_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_1_getItBlock__n1_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_1_getItBlock__n1_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_2_getItBlock__n2_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_2_getItBlock__n2_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_2_getItBlock__n2_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_2_getItBlock__n2_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_3_getItBlock__n3_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_3_getItBlock__n3_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_3_getItBlock__n3_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_expect_3_getItBlock__n3_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hideSelectors_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hideSelectors_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hideSelectors_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hideSelectors_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hover_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hover_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hover_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_hover_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_keyPressSelector_0_dividnavbar_0_Desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_keyPressSelector_0_dividnavbar_0_Desktop.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_1_viewport_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_1_viewport_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_1_viewport_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_magicSelectors_1_viewport_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_noDelay_0_getItBlocknth-child3_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_noDelay_0_getItBlocknth-child3_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_noDelay_0_getItBlocknth-child3_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_noDelay_0_getItBlocknth-child3_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notExpanded_0_getItBlock_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notExpanded_0_getItBlock_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notExpanded_0_getItBlock_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notExpanded_0_getItBlock_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notFound_0_monkey_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notFound_0_monkey_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notFound_0_monkey_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notFound_0_monkey_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notVisible_0_noShow_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notVisible_0_noShow_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notVisible_0_noShow_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_notVisible_0_noShow_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_0_pkratest_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_0_pkratest_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_0_pkratest_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_0_pkratest_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_1_logoBlock_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_1_logoBlock_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_1_logoBlock_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_pkra_bug_test_1_logoBlock_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEventTimeout_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEventTimeout_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEventTimeout_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEventTimeout_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEvent_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEvent_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEvent_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readyEvent_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelectorTimeout_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelectorTimeout_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelectorTimeout_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelectorTimeout_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelector_0_moneyshot_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelector_0_moneyshot_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelector_0_moneyshot_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_readySelector_0_moneyshot_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_removeSelectors_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_removeSelectors_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_removeSelectors_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_removeSelectors_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withEmptyViewports_0_document_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withEmptyViewports_0_document_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withEmptyViewports_0_document_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withEmptyViewports_0_document_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_0_getItBlock_0_iPad-Pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_0_getItBlock_0_iPad-Pro.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_1_getItBlock__n1_0_iPad-Pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_1_getItBlock__n1_0_iPad-Pro.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_2_getItBlock__n2_0_iPad-Pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_2_getItBlock__n2_0_iPad-Pro.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_3_getItBlock__n3_0_iPad-Pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withExpandSelector_3_getItBlock__n3_0_iPad-Pro.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_0_Pixel-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_0_Pixel-2.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_1_Pixel2-XL.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_1_Pixel2-XL.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_2_iPhone-X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_2_iPhone-X.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_3_iPad-Pro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports-withMultipleViewports_0_document_3_iPad-Pro.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports_0_document_0_Galaxy-S5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports_0_document_0_Galaxy-S5.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports_1_viewport_0_Galaxy-S5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scenarioSpecificViewports_1_viewport_0_Galaxy-S5.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scrollToSelector_0_lemurFace_0_phone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scrollToSelector_0_lemurFace_0_phone.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scrollToSelector_0_lemurFace_1_tablet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/backstop_data/bitmaps_reference/puppet_backstop_features_scrollToSelector_0_lemurFace_1_tablet.png
--------------------------------------------------------------------------------
/test/configs/backstop_data/cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": ".garris.github.io",
4 | "path": "/",
5 | "name": "theLemurHasLanded",
6 | "value": "true",
7 | "expirationDate": 1798790400,
8 | "hostOnly": false,
9 | "httpOnly": false,
10 | "secure": false,
11 | "session": false,
12 | "sameSite": "Lax"
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/cookies.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "domain": ".www.yourdomain.com",
4 | "path": "/",
5 | "name": "yourCookieName",
6 | "value": "yourCookieValue",
7 | "expirationDate": 1798790400,
8 | "hostOnly": false,
9 | "httpOnly": false,
10 | "secure": false,
11 | "session": false,
12 | "sameSite": "Lax"
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = function (engine, scenario, vp) {
2 | // This script runs before your app loads. Edit here to log-in, load cookies or set other states required for your test.
3 | console.log('onBefore.js has run for ' + vp.label + '.');
4 | };
5 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = function (engine, scenario, vp) {
2 | engine.evaluate(function () {
3 | // Your web-app is now loaded. Edit here to simulate user interactions or other state changes in the browser window context.
4 | });
5 | console.log('onReady.js has run for: ', vp.label);
6 | };
7 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/playwright/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario) => {
2 | const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3 | const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4 | const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5 | const scrollToSelector = scenario.scrollToSelector;
6 | const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7 |
8 | if (keyPressSelector) {
9 | for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10 | await page.waitForSelector(keyPressSelectorItem.selector);
11 | await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12 | }
13 | }
14 |
15 | if (hoverSelector) {
16 | for (const hoverSelectorIndex of [].concat(hoverSelector)) {
17 | await page.waitForSelector(hoverSelectorIndex);
18 | await page.hover(hoverSelectorIndex);
19 | }
20 | }
21 |
22 | if (clickSelector) {
23 | for (const clickSelectorIndex of [].concat(clickSelector)) {
24 | await page.waitForSelector(clickSelectorIndex);
25 | await page.click(clickSelectorIndex);
26 | }
27 | }
28 |
29 | if (postInteractionWait) {
30 | if (parseInt(postInteractionWait) > 0) {
31 | await page.waitForTimeout(postInteractionWait);
32 | } else {
33 | await page.waitForSelector(postInteractionWait);
34 | }
35 | }
36 |
37 | if (scrollToSelector) {
38 | await page.waitForSelector(scrollToSelector);
39 | await page.evaluate(scrollToSelector => {
40 | document.querySelector(scrollToSelector).scrollIntoView();
41 | }, scrollToSelector);
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/playwright/interceptImages.js:
--------------------------------------------------------------------------------
1 | /**
2 | * INTERCEPT IMAGES
3 | * Listen to all requests. If a request matches IMAGE_URL_RE
4 | * then stub the image with data from IMAGE_STUB_URL
5 | *
6 | * Use this in an onBefore script E.G.
7 | ```
8 | module.exports = async function(page, scenario) {
9 | require('./interceptImages')(page, scenario);
10 | }
11 | ```
12 | *
13 | */
14 |
15 | const fs = require('fs');
16 | const path = require('path');
17 |
18 | const IMAGE_URL_RE = /\.gif|\.jpg|\.png/i;
19 | const IMAGE_STUB_URL = path.resolve(__dirname, '../../imageStub.jpg');
20 | const IMAGE_DATA_BUFFER = fs.readFileSync(IMAGE_STUB_URL);
21 | const HEADERS_STUB = {};
22 |
23 | module.exports = async function (page, scenario) {
24 | page.route(IMAGE_URL_RE, route => {
25 | route.fulfill({
26 | body: IMAGE_DATA_BUFFER,
27 | headers: HEADERS_STUB,
28 | status: 200
29 | });
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/playwright/loadCookies.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (browserContext, scenario) => {
4 | let cookies = [];
5 | const cookiePath = scenario.cookiePath;
6 |
7 | // Read Cookies from File, if exists
8 | if (fs.existsSync(cookiePath)) {
9 | cookies = JSON.parse(fs.readFileSync(cookiePath));
10 | }
11 |
12 | // Add cookies to browser
13 | browserContext.addCookies(cookies);
14 |
15 | console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
16 | };
17 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/playwright/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, viewport, isReference, browserContext) => {
2 | await require('./loadCookies')(browserContext, scenario);
3 | };
4 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/playwright/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, viewport, isReference, browserContext) => {
2 | console.log('SCENARIO > ' + scenario.label);
3 | await require('./clickAndHoverHelper')(page, scenario);
4 |
5 | // add more ready handlers here...
6 | };
7 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/playwright/overrideCSS.js:
--------------------------------------------------------------------------------
1 | /**
2 | * OVERRIDE CSS
3 | * Apply this CSS to the loaded page, as a way to override styles.
4 | *
5 | * Use this in an onReady script E.G.
6 | ```
7 | module.exports = async function(page, scenario) {
8 | await require('./overrideCSS')(page, scenario);
9 | }
10 | ```
11 | *
12 | */
13 |
14 | const BACKSTOP_TEST_CSS_OVERRIDE = `
15 | html {
16 | background-image: none;
17 | }
18 | `;
19 |
20 | module.exports = async (page, scenario) => {
21 | // inject arbitrary css to override styles
22 | await page.addStyleTag({
23 | content: BACKSTOP_TEST_CSS_OVERRIDE
24 | });
25 |
26 | console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label);
27 | };
28 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/puppet/clickAndHoverHelper.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario) => {
2 | const hoverSelector = scenario.hoverSelectors || scenario.hoverSelector;
3 | const clickSelector = scenario.clickSelectors || scenario.clickSelector;
4 | const keyPressSelector = scenario.keyPressSelectors || scenario.keyPressSelector;
5 | const scrollToSelector = scenario.scrollToSelector;
6 | const postInteractionWait = scenario.postInteractionWait; // selector [str] | ms [int]
7 |
8 | if (keyPressSelector) {
9 | for (const keyPressSelectorItem of [].concat(keyPressSelector)) {
10 | await page.waitForSelector(keyPressSelectorItem.selector);
11 | await page.type(keyPressSelectorItem.selector, keyPressSelectorItem.keyPress);
12 | }
13 | }
14 |
15 | if (hoverSelector) {
16 | await page.waitForSelector(hoverSelector);
17 | await page.hover(hoverSelector);
18 | }
19 |
20 | if (clickSelector) {
21 | await page.waitForSelector(clickSelector);
22 | await page.click(clickSelector);
23 | }
24 |
25 | if (postInteractionWait) {
26 | await new Promise(resolve => {
27 | setTimeout(resolve, postInteractionWait);
28 | });
29 | }
30 |
31 | if (scrollToSelector) {
32 | await page.waitForSelector(scrollToSelector);
33 | await page.evaluate(scrollToSelector => {
34 | document.querySelector(scrollToSelector).scrollIntoView();
35 | }, scrollToSelector);
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/puppet/loadCookies.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = async (page, scenario) => {
4 | let cookies = [];
5 | const cookiePath = scenario.cookiePath;
6 |
7 | // READ COOKIES FROM FILE IF EXISTS
8 | if (fs.existsSync(cookiePath)) {
9 | cookies = JSON.parse(fs.readFileSync(cookiePath));
10 | }
11 |
12 | // // MUNGE COOKIE DOMAIN
13 | // cookies = cookies.map(cookie => {
14 | // cookie.url = 'https://' + cookie.domain;
15 | // delete cookie.domain;
16 | // return cookie;
17 | // });
18 |
19 | // SET COOKIES
20 | const setCookies = async () => {
21 | return Promise.all(
22 | cookies.map(async (cookie) => {
23 | await page.setCookie(cookie);
24 | })
25 | );
26 | };
27 |
28 | await setCookies();
29 | console.log('Cookie state restored with:', JSON.stringify(cookies, null, 2));
30 | };
31 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/puppet/onBefore.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | await require('./loadCookies')(page, scenario);
3 | };
4 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/puppet/onReady.js:
--------------------------------------------------------------------------------
1 | module.exports = async (page, scenario, vp) => {
2 | console.log('SCENARIO > ' + scenario.label);
3 | await require('./clickAndHoverHelper')(page, scenario);
4 | await require('./overrideCSS')(page, scenario);
5 |
6 | // add more ready handlers here...
7 | };
8 |
--------------------------------------------------------------------------------
/test/configs/backstop_data/engine_scripts/puppet/overrideCSS.js:
--------------------------------------------------------------------------------
1 | const BACKSTOP_TEST_CSS_OVERRIDE = 'html {background-image: none;}';
2 |
3 | module.exports = async (page, scenario) => {
4 | // inject arbitrary css to override styles
5 | await page.evaluate(`window._styleData = '${BACKSTOP_TEST_CSS_OVERRIDE}'`);
6 | await page.evaluate(() => {
7 | const style = document.createElement('style');
8 | style.type = 'text/css';
9 | const styleNode = document.createTextNode(window._styleData);
10 | style.appendChild(styleNode);
11 | document.head.appendChild(style);
12 | });
13 |
14 | console.log('BACKSTOP_TEST_CSS_OVERRIDE injected for: ' + scenario.label);
15 | };
16 |
--------------------------------------------------------------------------------
/test/configs/backstop_fail_cases.js:
--------------------------------------------------------------------------------
1 | /**
2 | * PUT ALL 'FAILING' TESTS IN HERE
3 | */
4 |
5 | const ENGINE = 'puppet';
6 | const SCRIPT_PATH = 'puppet';
7 |
8 | module.exports = {
9 | id: `${ENGINE}_backstop_features`,
10 | viewports: [
11 | {
12 | label: 'phone',
13 | width: 320,
14 | height: 480
15 | },
16 | {
17 | label: 'tablet',
18 | width: 1024,
19 | height: 768
20 | }
21 | ],
22 | onBeforeScript: `${SCRIPT_PATH}/onBefore.js`,
23 | onReadyScript: `${SCRIPT_PATH}/onReady.js`,
24 | scenarios: [
25 | {
26 | label: 'Simple',
27 | url: '../../index.html',
28 | selectors: ['.doesNotExist']
29 | },
30 | {
31 | label: 'click',
32 | url: '../../index.html?click',
33 | clickSelector: '#alsoDoesNotExist',
34 | selectors: ['.moneyshot']
35 | },
36 | {
37 | label: 'expect',
38 | url: '../../index.html',
39 | selectors: ['p'],
40 | selectorExpansion: true,
41 | expect: 8
42 | }
43 | ],
44 | paths: {
45 | bitmaps_reference: 'backstop_data/bitmaps_reference',
46 | bitmaps_test: 'backstop_data/bitmaps_test',
47 | engine_scripts: 'backstop_data/engine_scripts',
48 | html_report: 'backstop_data/html_report',
49 | ci_report: 'backstop_data/ci_report'
50 | },
51 | report: ['browser'],
52 | engine: ENGINE,
53 | engineOptions: {},
54 | asyncCaptureLimit: 10,
55 | asyncCompareLimit: 50,
56 | debug: false,
57 | debugWindow: false
58 | };
59 |
--------------------------------------------------------------------------------
/test/configs/lemurs/lemur-ring-tailed-lemur-primate-mammal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/lemurs/lemur-ring-tailed-lemur-primate-mammal.jpg
--------------------------------------------------------------------------------
/test/configs/lemurs/pexels-photo-145940.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/lemurs/pexels-photo-145940.jpg
--------------------------------------------------------------------------------
/test/configs/lemurs/pexels-photo-146006.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/lemurs/pexels-photo-146006.jpg
--------------------------------------------------------------------------------
/test/configs/lemurs/pexels-photo-146039.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/lemurs/pexels-photo-146039.jpg
--------------------------------------------------------------------------------
/test/configs/lemurs/pexels-photo-146049.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/configs/lemurs/pexels-photo-146049.jpg
--------------------------------------------------------------------------------
/test/configs/playwright.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "backstop_playwright",
3 | "viewports": [
4 | {
5 | "label": "phone",
6 | "width": 320,
7 | "height": 480
8 | },
9 | {
10 | "label": "tablet",
11 | "width": 1024,
12 | "height": 768
13 | }
14 | ],
15 | "onBeforeScript": "playwright/onBefore.js",
16 | "onReadyScript": "playwright/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "BackstopJS Homepage",
20 | "cookiePath": "backstop_data/engine_scripts/cookies.json",
21 | "url": "https://garris.github.io/BackstopJS/",
22 | "referenceUrl": "",
23 | "readyEvent": "",
24 | "readySelector": "",
25 | "delay": 0,
26 | "hideSelectors": [],
27 | "removeSelectors": [],
28 | "hoverSelector": "",
29 | "clickSelector": "",
30 | "postInteractionWait": 0,
31 | "selectors": [],
32 | "selectorExpansion": true,
33 | "misMatchThreshold" : 0.1,
34 | "requireSameDimensions": true
35 | }
36 | ],
37 | "paths": {
38 | "bitmaps_reference": "backstop_data/bitmaps_reference",
39 | "bitmaps_test": "backstop_data/bitmaps_test",
40 | "engine_scripts": "backstop_data/engine_scripts",
41 | "html_report": "backstop_data/html_report",
42 | "ci_report": "backstop_data/ci_report"
43 | },
44 | "report": ["browser"],
45 | "engine": "playwright",
46 | "engineOptions": {
47 | "args": ["--no-sandbox"]
48 | },
49 | "asyncCaptureLimit": 5,
50 | "asyncCompareLimit": 50,
51 | "debug": false,
52 | "debugWindow": false,
53 | "archiveReport": true,
54 | "scenarioLogsInReports": true
55 | }
56 |
--------------------------------------------------------------------------------
/test/configs/remote.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser, node */
2 |
3 | module.exports = {
4 | id: 'backstop-remote',
5 | viewports: [
6 | {
7 | label: 'webview',
8 | width: 1440,
9 | height: 900
10 | }
11 | ],
12 | onReadyScript: 'puppet/onReady.js',
13 | scenarios: [
14 | {
15 | label: '{testName}',
16 | url: '{origin}/backstop/dview/{testId}/{scenarioId}',
17 | delay: 500
18 | }
19 | ],
20 | paths: {
21 | bitmaps_reference: 'backstop_data/bitmaps_reference',
22 | bitmaps_test: 'backstop_data/bitmaps_test',
23 | engine_scripts: 'backstop_data/engine_scripts',
24 | html_report: 'backstop_data/html_report',
25 | ci_report: 'backstop_data/ci_report'
26 | },
27 | report: [],
28 | engine: 'puppet',
29 | engineOptions: {
30 | args: ['--no-sandbox']
31 | },
32 | asyncCaptureLimit: 10,
33 | asyncCompareLimit: 50,
34 | debug: false,
35 | debugWindow: false
36 | };
37 |
--------------------------------------------------------------------------------
/test/configs/responsiveDemo.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "responsiveDemoPage",
3 | "viewports": [
4 | {
5 | "label": "phone",
6 | "width": 320,
7 | "height": 480
8 | },
9 | {
10 | "label": "tablet",
11 | "width": 1024,
12 | "height": 768
13 | }
14 | ],
15 | "onBeforeScript": "puppet/onBefore.js",
16 | "onReadyScript": "puppet/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "comparePage",
20 | "cookiePath": "backstop_data/engine_scripts/cookies.json",
21 | "url": "http://127.0.0.1:3000/responsiveDemoAlias/index.html",
22 | "referenceUrl": "",
23 | "readyEvent": "",
24 | "readySelector": "",
25 | "delay": 100,
26 | "hideSelectors": [],
27 | "removeSelectors": [],
28 | "hoverSelector": "",
29 | "clickSelector": "",
30 | "postInteractionWait": 0,
31 | "selectors": [],
32 | "selectorExpansion": true,
33 | "expect": 0,
34 | "misMatchThreshold" : 0.1,
35 | "requireSameDimensions": true
36 | }
37 | ],
38 | "paths": {
39 | "bitmaps_reference": "backstop_data/bitmaps_reference",
40 | "bitmaps_test": "backstop_data/bitmaps_test",
41 | "engine_scripts": "backstop_data/engine_scripts",
42 | "html_report": "backstop_data/html_report",
43 | "ci_report": "backstop_data/ci_report"
44 | },
45 | "report": ["browser"],
46 | "engine": "puppeteer",
47 | "engineOptions": {
48 | "args": ["--no-sandbox"]
49 | },
50 | "asyncCaptureLimit": 5,
51 | "asyncCompareLimit": 50,
52 | "debug": false,
53 | "debugWindow": false
54 | }
55 |
--------------------------------------------------------------------------------
/test/configs/responsiveDemoAlias:
--------------------------------------------------------------------------------
1 | ../../examples/responsiveDemo
--------------------------------------------------------------------------------
/test/configs/responsiveTest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "responsiveDemoPage",
3 | "viewports": [
4 | {
5 | "label": "phone",
6 | "width": 320,
7 | "height": 480
8 | },
9 | {
10 | "label": "tablet",
11 | "width": 1024,
12 | "height": 768
13 | }
14 | ],
15 | "onBeforeScript": "puppet/onBefore.js",
16 | "onReadyScript": "puppet/onReady.js",
17 | "scenarios": [
18 | {
19 | "label": "comparePage",
20 | "url": "../../examples/responsiveDemo/index.html",
21 | "referenceUrl": "",
22 | "readyEvent": "",
23 | "readySelector": "",
24 | "delay": 100,
25 | "hideSelectors": [],
26 | "removeSelectors": [],
27 | "hoverSelector": "",
28 | "clickSelector": "",
29 | "postInteractionWait": 0,
30 | "selectors": [],
31 | "selectorExpansion": true,
32 | "expect": 0,
33 | "misMatchThreshold" : 0.1,
34 | "requireSameDimensions": true
35 | }
36 | ],
37 | "paths": {
38 | "bitmaps_reference": "backstop_data/bitmaps_reference",
39 | "bitmaps_test": "backstop_data/bitmaps_test",
40 | "engine_scripts": "backstop_data/engine_scripts",
41 | "html_report": "backstop_data/html_report",
42 | "ci_report": "backstop_data/ci_report"
43 | },
44 | "report": ["browser"],
45 | "engine": "puppeteer",
46 | "engineOptions": {
47 | "args": ["--no-sandbox"]
48 | },
49 | "asyncCaptureLimit": 5,
50 | "asyncCompareLimit": 50,
51 | "debug": false,
52 | "debugWindow": false
53 | }
54 |
--------------------------------------------------------------------------------
/test/configs/runFromNode.js:
--------------------------------------------------------------------------------
1 | // Go get a hook to BackstopJS
2 | const backstop = require('../../core/runner');
3 |
4 | // Run BackstopJS with docker
5 | // NOTE: passing either config file name or actual config object is supported.
6 | backstop('reference', {
7 | docker: false,
8 | config: 'backstop',
9 | filter: undefined,
10 | i: false
11 | }).then(
12 | () => console.log('nothing new'),
13 | () => console.log('changes found')
14 | );
15 |
--------------------------------------------------------------------------------
/test/configs/special_cases/scrollToSelector.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
23 |
24 |
25 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/test/configs/special_cases/scrollToSelector_puppet.js:
--------------------------------------------------------------------------------
1 | const ENGINE = 'puppet';
2 | const SCRIPT_PATH = 'puppet';
3 |
4 | module.exports = {
5 | id: `${ENGINE}_special_cases`,
6 | viewports: [
7 | {
8 | label: 'phone',
9 | width: 320,
10 | height: 480
11 | },
12 | {
13 | label: 'tablet',
14 | width: 1024,
15 | height: 768
16 | }
17 | ],
18 | onBeforeScript: `${SCRIPT_PATH}/onBefore.js`,
19 | onReadyScript: `${SCRIPT_PATH}/onReady.js`,
20 | scenarios: [
21 | {
22 | label: 'scrollTo',
23 | url: './scrollToSelector.html',
24 | scrollToSelector: '.lemurFace',
25 | selectors: ['.lemurFace']
26 | }
27 | ],
28 | paths: {
29 | bitmaps_reference: '../backstop_data/bitmaps_reference',
30 | bitmaps_test: '../backstop_data/bitmaps_test',
31 | engine_scripts: '../backstop_data/engine_scripts',
32 | html_report: '../backstop_data/html_report',
33 | ci_report: '../backstop_data/ci_report'
34 | },
35 | report: ['browser'],
36 | engine: ENGINE,
37 | engineOptions: {},
38 | asyncCaptureLimit: 10,
39 | asyncCompareLimit: 50,
40 | debug: false,
41 | debugWindow: false
42 | };
43 |
--------------------------------------------------------------------------------
/test/core/runner_spec.js:
--------------------------------------------------------------------------------
1 | const mockery = require('mockery');
2 | const assert = require('assert');
3 |
4 | describe('the runner', function () {
5 | before(function () {
6 | mockery.registerMock('./util/makeConfig', function (command, args) {
7 | return { command, args };
8 | });
9 | mockery.registerMock('./command/', function (command, config) {
10 | return Promise.resolve({ command, config });
11 | });
12 | mockery.enable({ warnOnUnregistered: false });
13 | });
14 |
15 | after(function () {
16 | mockery.disable();
17 | });
18 |
19 | it('should call the command/index with the correct config', function () {
20 | const runner = require('../../core/runner');
21 | return runner('test', {}).then(function (args) {
22 | assert.strictEqual(args.command, 'test');
23 | assert.deepStrictEqual(args.config, { command: 'test', args: {} });
24 | });
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/test/core/util/backstop.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/test/core/util/compare/compare-hash_spec.js:
--------------------------------------------------------------------------------
1 | const compareHash = require('../../../../core/util/compare/compare-hash');
2 | const path = require('path');
3 | const assert = require('assert');
4 |
5 | const REF_IMG1 = path.join(__dirname, 'refImage-1.png');
6 | const REF_IMG1_OPTIMIZED = path.join(__dirname, 'refImage-1-optimized.png');
7 | const REF_IMG2 = path.join(__dirname, 'refImage-2.png');
8 |
9 | describe('compare-hashes', function () {
10 | it('should resolve if the same image is compared', function () {
11 | const expectedResult = {
12 | isSameDimensions: true,
13 | dimensionDifference: { width: 0, height: 0 },
14 | misMatchPercentage: '0.00'
15 | };
16 |
17 | return compareHash(REF_IMG1, REF_IMG1)
18 | .then(data => assert.deepStrictEqual(data, expectedResult));
19 | });
20 | it('should reject if two images have the same content', function (cb) {
21 | compareHash(REF_IMG1, REF_IMG1_OPTIMIZED).catch(() => cb());
22 | });
23 | it('should reject if two images exceed the mismatchThreshold', function (cb) {
24 | compareHash(REF_IMG1, REF_IMG2).catch(() => cb());
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/test/core/util/compare/compare-resemble_spec.js:
--------------------------------------------------------------------------------
1 | const compareResemble = require('../../../../core/util/compare/compare-resemble');
2 | const path = require('path');
3 |
4 | const REF_IMG1 = path.join(__dirname, 'refImage-1.png');
5 | const REF_IMG1_OPTIMIZED = path.join(__dirname, 'refImage-1-optimized.png');
6 | const REF_IMG2 = path.join(__dirname, 'refImage-2.png');
7 | const REF_IMG3 = path.join(__dirname, 'refImage-3.png');
8 |
9 | describe('compare-resemble', function () {
10 | it('should resolve if the same image is compared', function () {
11 | return compareResemble(REF_IMG1, REF_IMG1, 0, {});
12 | });
13 | it('should resolve if two images have the same content', function () {
14 | return compareResemble(REF_IMG1, REF_IMG1_OPTIMIZED, 0, {});
15 | });
16 | it('should reject if two images exceed the mismatchThreshold', function (cb) {
17 | compareResemble(REF_IMG1, REF_IMG2, 0, {}).then(
18 | () => cb(new Error('should have rejected')),
19 | () => cb()
20 | );
21 | });
22 | it('should use resemble\'s rounded misMatchPercentage value per default', function () {
23 | return compareResemble(REF_IMG1, REF_IMG3, 0, {});
24 | });
25 | it('should use resemble\'s more precise rawMisMatchPercentage value if specified', function (cb) {
26 | compareResemble(REF_IMG1, REF_IMG3, 0, { usePreciseMatching: true }, true).then(
27 | () => cb(new Error('should have rejected')),
28 | () => cb()
29 | );
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/test/core/util/compare/compare_spec.js:
--------------------------------------------------------------------------------
1 | const mockery = require('mockery');
2 | const sinon = require('sinon');
3 |
4 | describe('compare', function () {
5 | let compare;
6 | const compareHashes = sinon.stub();
7 | const compareResemble = sinon.stub();
8 | const error = new Error();
9 |
10 | before(function () {
11 | mockery.enable({ warnOnUnregistered: false, warnOnReplace: false, useCleanCache: true });
12 | mockery.registerMock('./compare-hash', compareHashes);
13 | mockery.registerMock('./compare-resemble', compareResemble);
14 | compare = require('../../../../core/util/compare/compare');
15 | });
16 |
17 | afterEach(() => {
18 | compareResemble.resetBehavior();
19 | compareHashes.resetBehavior();
20 | });
21 |
22 | after(function () {
23 | mockery.disable();
24 | });
25 |
26 | it.skip('should resolve if compare-hashes succeed', function () {
27 | compareHashes.withArgs('img1.png', 'img2.png').returns(Promise.resolve());
28 | compareResemble.returns(Promise.reject(error));
29 |
30 | return compare('img1.png', 'img2.png', 0, {});
31 | });
32 |
33 | it.skip('should resolve if compare-hashes fail, but compare-resemble succeeds', function () {
34 | compareHashes.returns(Promise.reject(error));
35 | compareResemble.withArgs('img1.png', 'img2.png', 0, {}).returns(Promise.resolve());
36 |
37 | return compare('img1.png', 'img2.png', 0, {});
38 | });
39 |
40 | it.skip('should reject if compare-hashes and compare-resemble fail', function (cb) {
41 | compareHashes.returns(Promise.reject(error));
42 | compareResemble.returns(Promise.reject(error));
43 |
44 | compare('img1.png', 'img2.png', 0, {}).catch(() => cb());
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/core/util/compare/refImage-1-optimized.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/core/util/compare/refImage-1-optimized.png
--------------------------------------------------------------------------------
/test/core/util/compare/refImage-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/core/util/compare/refImage-1.png
--------------------------------------------------------------------------------
/test/core/util/compare/refImage-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/core/util/compare/refImage-2.png
--------------------------------------------------------------------------------
/test/core/util/compare/refImage-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garris/BackstopJS/930b3c863d3946fd3c8156166692739479ad51c7/test/core/util/compare/refImage-3.png
--------------------------------------------------------------------------------
/test/core/util/engineErrors_spec.js:
--------------------------------------------------------------------------------
1 | const engineErrors = require('../../../core/util/engineErrors');
2 | const assert = require('assert');
3 |
4 | describe('core/util/engineErrors', function () {
5 | it('should resolve if no engineErrors errors found', function () {
6 | const config = {
7 | tempCompareConfigFileName: '../../test/core/util/fixtures/engineErrorsSuccess.json'
8 | };
9 | return engineErrors(config).then(function (args) {
10 | assert.strictEqual(args, undefined);
11 | });
12 | });
13 |
14 | it('should reject if engineErros found', function () {
15 | const config = {
16 | tempCompareConfigFileName: '../../test/core/util/fixtures/engineErrorsFail.json'
17 | };
18 | return engineErrors(config).catch(function (args) {
19 | assert.strictEqual(args.engineErrorMsg, 'Engine error fail');
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/test/core/util/extendConfig_spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const extendConfig = require('../../../core/util/extendConfig');
3 |
4 | describe('computeConfig_spec', function () {
5 | it('should override engine from config file', function () {
6 | const actualConfig = extendConfig({ projectPath: process.cwd(), backstop: process.cwd() }, { engine: 'puppet' });
7 | assert.strictEqual(actualConfig.engine, 'puppet');
8 | });
9 |
10 | it('should override resembleOutputOptions from config file', function () {
11 | const actualConfig = extendConfig({
12 | projectPath: process.cwd(),
13 | backstop: process.cwd()
14 | }, { resembleOutputOptions: { transparency: 0.3 } });
15 | assert.strictEqual(actualConfig.resembleOutputOptions.transparency, 0.3);
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/test/core/util/fixtures/engineErrorsFail.json:
--------------------------------------------------------------------------------
1 | {
2 | "compareConfig": {
3 | "testPairs": [
4 | {
5 | "engineErrorMsg": "Engine error fail"
6 | }
7 | ]
8 | }
9 | }
--------------------------------------------------------------------------------
/test/core/util/fixtures/engineErrorsSuccess.json:
--------------------------------------------------------------------------------
1 | {
2 | "compareConfig": {
3 | "testPairs": [
4 | {
5 | "engineErrorMsg": ""
6 | }
7 | ]
8 | }
9 | }
--------------------------------------------------------------------------------
/test/core/util/makeConfig_spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const makeConfig = require('../../../core/util/makeConfig');
3 |
4 | describe('make config', function () {
5 | it('should pass the filter arg correctly', function () {
6 | const actualConfig = makeConfig('init', { filter: true });
7 | assert.strictEqual(actualConfig.args.filter, true);
8 | });
9 |
10 | it('should work without an option param', function () {
11 | const actualConfig = makeConfig('init');
12 | assert.deepStrictEqual(actualConfig.args, {});
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/test/core/util/remote_spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { modifyJsonpReportHelper } = require('../../../core/util/remote');
3 |
4 | const originalJsonpReport = `report({
5 | "tests": [
6 | {
7 | "pair": {
8 | "fileName": "scenario_1.png"
9 | },
10 | "status": "pass"
11 | },
12 | {
13 | "pair": {
14 | "fileName": "scenario_2.png",
15 | "diffImage": "../bitmaps_test/20200217-233748/failed_diff_scenario_2.png"
16 | },
17 | "status": "fail"
18 | }
19 | ]
20 | });`;
21 |
22 | const expectedJsonpReport = `report({
23 | "tests": [
24 | {
25 | "pair": {
26 | "fileName": "scenario_1.png"
27 | },
28 | "status": "pass"
29 | },
30 | {
31 | "pair": {
32 | "fileName": "scenario_2.png"
33 | },
34 | "status": "pass"
35 | }
36 | ]
37 | });`;
38 |
39 | describe('Util | remote', function () {
40 | it('should change the approved test status to "pass"', function () {
41 | const jsonpReport = modifyJsonpReportHelper(originalJsonpReport, 'scenario_2.png');
42 | assert.deepStrictEqual(jsonpReport, expectedJsonpReport);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------