├── OSSMETADATA ├── cli ├── graalvm │ ├── proxy-config.json │ ├── serialization-config.json │ ├── predefined-classes-config.json │ ├── .sdkmanrc │ ├── lineup_chrome.json │ ├── lineup_chrome_headless.json │ ├── lineup_firefox_headless.json │ ├── java.security.overrides │ ├── resource-config.json │ ├── prepare-native-image-build.sh │ └── build-native-image.sh ├── src │ ├── test │ │ ├── resources │ │ │ ├── acceptance │ │ │ │ ├── acceptance_no_urls.lineup.json │ │ │ │ ├── webpage │ │ │ │ │ ├── test.html │ │ │ │ │ ├── otto_logo_2015.png │ │ │ │ │ ├── wintersale_progressive.jpeg │ │ │ │ │ ├── test_remove1.html │ │ │ │ │ ├── test_remove2.html │ │ │ │ │ ├── progressive_jpeg.html │ │ │ │ │ └── test_dom.html │ │ │ │ ├── acceptance.lineup.json │ │ │ │ ├── acceptance_legacy.lineup.json │ │ │ │ ├── acceptance_reportv2.lineup.json │ │ │ │ ├── acceptance_chrome.lineup.json │ │ │ │ ├── acceptance_firefox.lineup.json │ │ │ │ ├── acceptance_chrome-headless.lineup.json │ │ │ │ ├── acceptance-merge.lineup.json │ │ │ │ ├── acceptance_chrome-remove_selectors.lineup.json │ │ │ │ ├── acceptance_firefox_variant1.lineup.json │ │ │ │ ├── acceptance_firefox_variant2.lineup.json │ │ │ │ ├── acceptance_chrome-wait_for_selectors.lineup.json │ │ │ │ ├── acceptance_wrong_url_firefox.lineup.json │ │ │ │ ├── acceptance_long_url.lineup.json │ │ │ │ ├── acceptance_timeout.lineup.json │ │ │ │ ├── acceptance_chrome-wait_for_selectors_fails_but_no_error.lineup.json │ │ │ │ ├── acceptance_chrome-wait_for_selectors_fails_with_error.lineup.json │ │ │ │ ├── acceptance_wrong_url_chrome.lineup.json │ │ │ │ ├── acceptance_wrong_js.lineup.json │ │ │ │ ├── acceptance_chrome_svg.lineup.json │ │ │ │ ├── acceptance_chrome_progressive_jpg.lineup.json │ │ │ │ └── acceptance_chrome_devices.lineup.json │ │ │ └── settings.properties │ │ └── java │ │ │ └── de │ │ │ └── otto │ │ │ └── jlineup │ │ │ └── cli │ │ │ ├── RunRunStepConfigTest.java │ │ │ └── acceptance │ │ │ └── MirrorOutputStream.java │ └── main │ │ ├── java │ │ └── de │ │ │ └── otto │ │ │ └── jlineup │ │ │ └── cli │ │ │ ├── LogToFileFilter.java │ │ │ ├── Main.java │ │ │ └── Utils.java │ │ └── resources │ │ └── logback.xml └── build.gradle ├── MAINTAINERS ├── gradle.properties ├── docs ├── html-report.png ├── jlineup-logo.png ├── web │ ├── report1.png │ ├── report2.png │ ├── reports.png │ ├── reports2.png │ └── status.png ├── jlineup-logo-2024.png ├── pipeline-example.png ├── jlineup-logo_small.png ├── jlineup-repository-image.png ├── maven-central-publishing-failure.png ├── job.mermaid ├── FUNCTIONALITY.md ├── example-report │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00000_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00000_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00800_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00800_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_01600_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_01600_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00000_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00000_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_02400_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_02400_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00000_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00000_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00800_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00800_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_01600_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_01600_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_02400_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_02400_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00800_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00800_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_01600_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_01600_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_02400_after.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_02400_before.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_DIFFERENCE.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_DIFFERENCE.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_DIFFERENCE.png │ ├── https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_06400_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_02400_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_03200_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_05600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_05600_after.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_02400_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_03200_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_05600_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_before.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_DIFFERENCE.png │ ├── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_DIFFERENCE.png │ └── https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_DIFFERENCE.png └── pipeline-example.mermaid ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── core └── src │ ├── test │ ├── resources │ │ ├── lineup_minimal_test.json │ │ ├── screenshots │ │ │ ├── ideaWide.png │ │ │ ├── cases │ │ │ │ ├── diff.png │ │ │ │ ├── benefit_after.png │ │ │ │ ├── benefit_before.png │ │ │ │ ├── otto_logo_after.png │ │ │ │ ├── otto_logo_diff.png │ │ │ │ ├── otto_logo_diff2.png │ │ │ │ ├── otto_logo_diff3.png │ │ │ │ ├── shoppromo_after.png │ │ │ │ ├── otto_logo_before.png │ │ │ │ ├── otto_moebel_after.png │ │ │ │ ├── shoppromo_before.png │ │ │ │ ├── otto_moebel_before.png │ │ │ │ ├── otto_like_heart_after.png │ │ │ │ ├── otto_like_heart_before.png │ │ │ │ ├── otto_moebel_logo_after.png │ │ │ │ ├── otto_like_heart_compare.png │ │ │ │ ├── otto_moebel_logo_before.png │ │ │ │ ├── chrome_rounded_edges_after.png │ │ │ │ ├── chrome_rounded_edges_before.png │ │ │ │ ├── testAdditionalCombos_after.png │ │ │ │ ├── testAdditionalCombos_before.png │ │ │ │ ├── nordic_style_image_blur_after.png │ │ │ │ ├── nordic_style_image_blur_before.png │ │ │ │ ├── testAdditionalCombos_compare.png │ │ │ │ ├── chrome_rounded_edges_DIFFERENCE.png │ │ │ │ ├── nordic_style_image_blur_compare.png │ │ │ │ └── otto_like_heart_config.txt │ │ │ ├── less_height.png │ │ │ ├── more_height.png │ │ │ ├── ideaVertical.png │ │ │ ├── test_image_750x500.png │ │ │ ├── test_image_1125x750.png │ │ │ ├── ideaDifferenceReference.png │ │ │ ├── ideaDifferenceReferenceNew.png │ │ │ ├── http_url_root_ff3c40c_1001_02002_after.png │ │ │ ├── http_url_root_ff3c40c_1001_03003_after.png │ │ │ ├── http_url_root_ff3c40c_1001_02002_before.png │ │ │ └── http_url_root_ff3c40c_1001_02002_DIFFERENCE_reference.png │ │ ├── lineup.json │ │ ├── lineup_test_context_before.json │ │ ├── lineup_test_context_after.json │ │ └── lineup_test.json │ └── java │ │ └── de │ │ └── otto │ │ └── jlineup │ │ ├── browser │ │ ├── TestSupportWebDriver.java │ │ ├── WebConfiguration.java │ │ ├── LogErrorCheckerTest.java │ │ └── FakeWebServerController.java │ │ ├── image │ │ ├── LABTest.java │ │ └── AntiAliasingIgnoringComparatorTest.java │ │ ├── JLineupRunnerTest.java │ │ ├── config │ │ ├── ConfigMergerTest.java │ │ └── JobConfigValidatorTest.java │ │ ├── file │ │ └── FileUtilsTest.java │ │ └── report │ │ └── JSONReportWriterV1Test.java │ └── main │ ├── resources │ ├── version.properties │ └── templates │ │ └── report_not_finished.html │ └── java │ └── de │ └── otto │ └── jlineup │ ├── browser │ ├── BrowserStep.java │ ├── JLineupException.java │ ├── CloudBrowser.java │ ├── CloudBrowserFactory.java │ └── LogErrorChecker.java │ ├── config │ ├── JobConfigMixIn.java │ ├── UrlConfigMixIn.java │ ├── HttpCheckFilter.java │ ├── ReportFormatFilter.java │ ├── RunStep.java │ └── CustomDateDeserializer.java │ ├── Main.java │ ├── report │ ├── UsedInTemplate.java │ ├── JSONReportWriter.java │ ├── Report.java │ ├── JSONReportWriter_V2.java │ ├── JSONReportWriter_V1.java │ ├── UrlReport.java │ ├── UrlReportV2.java │ ├── Summary.java │ ├── ReportGenerator.java │ ├── ReportV2.java │ └── ReportGeneratorV2.java │ ├── exceptions │ └── ValidationError.java │ ├── GlobalOption.java │ ├── file │ └── FileUtils.java │ ├── GlobalOptions.java │ └── JacksonWrapper.java ├── .sdkmanrc ├── web ├── src │ ├── main │ │ ├── java │ │ │ └── de │ │ │ │ └── otto │ │ │ │ └── jlineup │ │ │ │ ├── web │ │ │ │ ├── Result.java │ │ │ │ ├── Step.java │ │ │ │ ├── Phase.java │ │ │ │ ├── RunBeforeResponse.java │ │ │ │ ├── configuration │ │ │ │ │ ├── JLineupWebLambdaProperties.java │ │ │ │ │ ├── JLineupConfiguration.java │ │ │ │ │ ├── NavigationConfiguration.java │ │ │ │ │ ├── JacksonConfiguration.java │ │ │ │ │ └── JLineupWebMvcConfigurationSupport.java │ │ │ │ ├── State.java │ │ │ │ └── JLineupWebApplication.java │ │ │ │ └── service │ │ │ │ ├── RunNotFoundException.java │ │ │ │ ├── BrowserNotInstalledException.java │ │ │ │ └── InvalidRunStateException.java │ │ └── resources │ │ │ ├── META-INF │ │ │ └── additional-spring-configuration-metadata.json │ │ │ ├── banner.txt │ │ │ ├── templates │ │ │ ├── error.html │ │ │ └── reports.html │ │ │ ├── application.yml │ │ │ └── logback.xml │ └── test │ │ ├── java │ │ └── de │ │ │ └── otto │ │ │ └── jlineup │ │ │ ├── web │ │ │ └── configuration │ │ │ │ └── JLineupTestConfiguration.java │ │ │ └── utils │ │ │ └── RegexMatcher.java │ │ └── resources │ │ └── application-test.yml ├── Dockerfile ├── docker │ └── application.yml └── build.gradle ├── lambda ├── src │ └── main │ │ └── java │ │ └── de │ │ └── otto │ │ └── jlineup │ │ └── lambda │ │ ├── Main.java │ │ └── LambdaRequestPayload.java └── build.gradle ├── test.gradle ├── .gitignore ├── settings.gradle ├── .run ├── JLineupWebApplication.run.xml ├── Main (before).run.xml ├── Main (compare).run.xml ├── Main (print-config).run.xml ├── Main (help).run.xml ├── Main (example).run.xml ├── Main (after_only).run.xml ├── Main (before keep existing).run.xml ├── Main (before merge) .run.xml ├── Main (after, keep existing).run.xml ├── Main (merge print-config).run.xml ├── Main (before keep existing refresh url).run.xml └── Main (after).run.xml ├── web-lambda └── build.gradle ├── cli-lambda └── build.gradle ├── .github ├── dependabot.yml └── workflows │ └── delete-old-packages.yml ├── release.sh ├── gradlew.bat └── dependency-check-suppressions.xml /OSSMETADATA: -------------------------------------------------------------------------------- 1 | osslifecycle=active 2 | -------------------------------------------------------------------------------- /cli/graalvm/proxy-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /MAINTAINERS: -------------------------------------------------------------------------------- 1 | Marco Geweke 2 | -------------------------------------------------------------------------------- /cli/graalvm/serialization-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | ] 3 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_no_urls.lineup.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /cli/src/test/resources/settings.properties: -------------------------------------------------------------------------------- 1 | jlineup.chrome-version=141 -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4096m 2 | org.gradle.daemon=false -------------------------------------------------------------------------------- /docs/html-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/html-report.png -------------------------------------------------------------------------------- /docs/jlineup-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/jlineup-logo.png -------------------------------------------------------------------------------- /docs/web/report1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/web/report1.png -------------------------------------------------------------------------------- /docs/web/report2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/web/report2.png -------------------------------------------------------------------------------- /docs/web/reports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/web/reports.png -------------------------------------------------------------------------------- /docs/web/reports2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/web/reports2.png -------------------------------------------------------------------------------- /docs/web/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/web/status.png -------------------------------------------------------------------------------- /docs/jlineup-logo-2024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/jlineup-logo-2024.png -------------------------------------------------------------------------------- /docs/pipeline-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/pipeline-example.png -------------------------------------------------------------------------------- /docs/jlineup-logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/jlineup-logo_small.png -------------------------------------------------------------------------------- /docs/jlineup-repository-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/jlineup-repository-image.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /core/src/test/resources/lineup_minimal_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.otto.de": { 4 | } 5 | } 6 | } -------------------------------------------------------------------------------- /.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=21.0.7-tem 4 | -------------------------------------------------------------------------------- /docs/maven-central-publishing-failure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/maven-central-publishing-failure.png -------------------------------------------------------------------------------- /core/src/main/resources/version.properties: -------------------------------------------------------------------------------- 1 | jlineup.version=Version set by gradle build 2 | jlineup.commit=Commit hash set by gradle build -------------------------------------------------------------------------------- /cli/graalvm/predefined-classes-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type":"agent-extracted", 4 | "classes":[ 5 | ] 6 | } 7 | ] 8 | 9 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/test.html: -------------------------------------------------------------------------------- 1 | 2 | Testpage 3 |

Hallo!

4 | -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/ideaWide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/ideaWide.png -------------------------------------------------------------------------------- /cli/graalvm/.sdkmanrc: -------------------------------------------------------------------------------- 1 | # Enable auto-env through the sdkman_auto_env config 2 | # Add key=value pairs of SDKs to use below 3 | java=21.3.0.r11-grl 4 | -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/diff.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/less_height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/less_height.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/more_height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/more_height.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/ideaVertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/ideaVertical.png -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/Result.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web; 2 | 3 | public enum Result { 4 | UNCHANGED, 5 | DIFFERENCES 6 | } 7 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/Step.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web; 2 | 3 | public enum Step { 4 | 5 | BEFORE, 6 | AFTER 7 | 8 | } 9 | -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/test_image_750x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/test_image_750x500.png -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/otto_logo_2015.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/cli/src/test/resources/acceptance/webpage/otto_logo_2015.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/benefit_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/benefit_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/benefit_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/benefit_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_logo_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_logo_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_logo_diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_logo_diff.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_logo_diff2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_logo_diff2.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_logo_diff3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_logo_diff3.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/shoppromo_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/shoppromo_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/test_image_1125x750.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/test_image_1125x750.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_logo_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_logo_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_moebel_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_moebel_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/shoppromo_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/shoppromo_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/ideaDifferenceReference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/ideaDifferenceReference.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_moebel_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_moebel_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/ideaDifferenceReferenceNew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/ideaDifferenceReferenceNew.png -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/browser/BrowserStep.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | public enum BrowserStep { 4 | before, 5 | after, 6 | compare 7 | } 8 | -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_like_heart_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_like_heart_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_like_heart_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_like_heart_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_moebel_logo_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_moebel_logo_after.png -------------------------------------------------------------------------------- /docs/job.mermaid: -------------------------------------------------------------------------------- 1 | graph TD 2 | subgraph Job 3 | subgraph Run step 4 | A[before] 5 | end 6 | subgraph Run step 7 | B[after] --> C[compare] 8 | end 9 | end 10 | 11 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/wintersale_progressive.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/cli/src/test/resources/acceptance/webpage/wintersale_progressive.jpeg -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_like_heart_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_like_heart_compare.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_moebel_logo_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/otto_moebel_logo_before.png -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/test_remove1.html: -------------------------------------------------------------------------------- 1 | 2 | Testpage 3 |

Hallo!

This will be removed.

4 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/test_remove2.html: -------------------------------------------------------------------------------- 1 | 2 | Testpage 3 |

Hallo!

This will be removed.

4 | -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/chrome_rounded_edges_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/chrome_rounded_edges_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/chrome_rounded_edges_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/chrome_rounded_edges_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/testAdditionalCombos_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/testAdditionalCombos_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/testAdditionalCombos_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/testAdditionalCombos_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/nordic_style_image_blur_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/nordic_style_image_blur_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/nordic_style_image_blur_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/nordic_style_image_blur_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/testAdditionalCombos_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/testAdditionalCombos_compare.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/chrome_rounded_edges_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/chrome_rounded_edges_DIFFERENCE.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/nordic_style_image_blur_compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/cases/nordic_style_image_blur_compare.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_02002_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_02002_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_03003_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_03003_after.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_02002_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_02002_before.png -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/Phase.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web; 2 | 3 | public enum Phase { 4 | 5 | PENDING, 6 | RUNNING, 7 | DONE, 8 | ERROR, 9 | DEAD, 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /docs/FUNCTIONALITY.md: -------------------------------------------------------------------------------- 1 | # Functionality 2 | 3 | If you are interested in the mode of operation of JLineup, this document is for you! 4 | 5 | ``` mermaid 6 | 7 | graph TD 8 | A-->B 9 | 10 | ``` 11 | 12 | === TODO: Fill this -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1200_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_02400_before.png -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_02002_DIFFERENCE_reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/core/src/test/resources/screenshots/http_url_root_ff3c40c_1001_02002_DIFFERENCE_reference.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_0500_02400_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_00800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1000_01600_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__k_10072934231_a_224673_6a559b0_1600_00000_DIFFERENCE.png -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/config/JobConfigMixIn.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | 5 | @JsonInclude(JsonInclude.Include.NON_NULL) 6 | public class JobConfigMixIn { 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/config/UrlConfigMixIn.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | 5 | @JsonInclude(JsonInclude.Include.ALWAYS) 6 | public class UrlConfigMixIn { 7 | } 8 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/progressive_jpeg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | JPEG Testpage 4 | 5 | 6 |

Progressive JPEG test page

7 | 8 | 9 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/browser/JLineupException.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | public class JLineupException extends Exception { 4 | public JLineupException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_06400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_06400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_03200_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_03200_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_05600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_05600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_after.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_05600_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_05600_after.png -------------------------------------------------------------------------------- /lambda/src/main/java/de/otto/jlineup/lambda/Main.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.lambda; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) { 6 | //de.otto.jlineup.cli.Main.main(args); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_03200_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_03200_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_04800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_05600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_05600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_before.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_before.png -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_legacy.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ] 7 | } 8 | }, 9 | "report-format" : 1 10 | } -------------------------------------------------------------------------------- /core/src/test/resources/screenshots/cases/otto_like_heart_config.txt: -------------------------------------------------------------------------------- 1 | "max-diff": 0.0, 2 | "ignore-anti-aliasing": true, 3 | "strict-color-comparison": false, 4 | "max-color-distance": 3.6 5 | } 6 | }, 7 | "browser": "chrome-headless", -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00000_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_00800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_01600_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_02400_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_03200_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04000_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_04800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_0500_05600_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00000_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_00800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_01600_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_02400_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_03200_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04000_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1000_04800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_00800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1200_01600_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00000_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_00800_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_01600_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_02400_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_03200_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04000_DIFFERENCE.png -------------------------------------------------------------------------------- /docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_DIFFERENCE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otto-de/jlineup/HEAD/docs/example-report/https_www_otto_de__wohnen__thema_thmn123nol_retro_chic_9465bea_1600_04800_DIFFERENCE.png -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_reportv2.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ] 7 | } 8 | }, 9 | "report-format" : 2 10 | } -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/Main.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | System.out.println("The core module has no standalone functionality. Use jlineup-cli or jlineup-web!"); 6 | } 7 | 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ] 7 | } 8 | }, 9 | "report-format" : 2, 10 | "browser" : "chrome" 11 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_firefox.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ] 7 | } 8 | }, 9 | "report-format" : 2, 10 | "browser" : "firefox" 11 | } -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/config/HttpCheckFilter.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | public class HttpCheckFilter { 4 | 5 | @Override 6 | public boolean equals(Object obj) { 7 | return obj == null || obj.equals(new HttpCheckConfig()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test.gradle: -------------------------------------------------------------------------------- 1 | // 2 | // Run with `./gradlew -b test.gradle dependencies` to check correctly released poms for JLineup 3 | // 4 | apply plugin: 'java' 5 | 6 | repositories { 7 | mavenLocal() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | runtime "de.otto:jlineup:+" 13 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome-headless.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ] 7 | } 8 | }, 9 | "report-format" : 2, 10 | "browser" : "chrome-headless" 11 | } -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/UsedInTemplate.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | /** 4 | * This annotation can be used to mark functions that are only used in frontend templates. 5 | * You can tell your IDE to not report those functions as unused. 6 | */ 7 | public @interface UsedInTemplate { 8 | } 9 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/RunBeforeResponse.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web; 2 | 3 | public class RunBeforeResponse { 4 | private final String id; 5 | 6 | public RunBeforeResponse(String id) { 7 | this.id = id; 8 | } 9 | public String getId() { 10 | return id; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/exceptions/ValidationError.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.exceptions; 2 | 3 | public class ValidationError extends RuntimeException { 4 | public ValidationError(String message) { 5 | super("Error during job config validation:\n" + message + "\nPlease check your jlineup job config."); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /web/src/test/java/de/otto/jlineup/web/configuration/JLineupTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web.configuration; 2 | 3 | import de.otto.jlineup.service.JLineupService; 4 | import org.mockito.Mock; 5 | 6 | public class JLineupTestConfiguration { 7 | 8 | @Mock 9 | public JLineupService jLineupService; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance-merge.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | ".*": { 4 | "paths": [ 5 | "test_remove1.html" 6 | ], 7 | "devices": [ 8 | { 9 | "device-name": "DESKTOP", 10 | "width": 1234, 11 | "height": 999 12 | } 13 | ] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome-remove_selectors.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/test_remove###NUM###.html": { 4 | "remove-selectors": [ 5 | ".removeMe", 6 | "#removeMeToo" 7 | ] 8 | } 9 | }, 10 | "browser" : "chrome-headless" 11 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_firefox_variant1.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ], 7 | "javascript": "console.log('1');" 8 | } 9 | }, 10 | "report-format" : 2, 11 | "browser" : "firefox-headless" 12 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_firefox_variant2.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ], 7 | "javascript": "console.log('2');" 8 | } 9 | }, 10 | "report-format" : 2, 11 | "browser" : "firefox-headless" 12 | } -------------------------------------------------------------------------------- /core/src/test/resources/lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "http://www.clocktab.com": { 4 | "paths": [ 5 | "/" 6 | ], 7 | "max-diff": 0.05, 8 | "resolutions": [ 9 | 1200 10 | ], 11 | "max-scroll-height":50000 12 | } 13 | }, 14 | "browser": "chrome-headless", 15 | "wait-after-page-load": 1 16 | } -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/service/RunNotFoundException.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.service; 2 | 3 | public class RunNotFoundException extends Exception { 4 | 5 | private final String id; 6 | 7 | public RunNotFoundException(String id) { 8 | this.id = id; 9 | } 10 | 11 | public String getId() { 12 | return id; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/config/ReportFormatFilter.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import static de.otto.jlineup.config.JobConfig.DEFAULT_REPORT_FORMAT; 4 | 5 | public class ReportFormatFilter { 6 | 7 | @Override 8 | public boolean equals(Object obj) { 9 | return obj == null || obj.equals(DEFAULT_REPORT_FORMAT); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/JSONReportWriter.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | 5 | import java.io.FileNotFoundException; 6 | 7 | public interface JSONReportWriter { 8 | 9 | void writeComparisonReportAsJson(Report report) throws FileNotFoundException, JsonProcessingException; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | *.iws 4 | *.ipr 5 | .gradle 6 | out 7 | **/target 8 | **/*.log 9 | build 10 | classes 11 | /lineup*.json 12 | /report 13 | /report/** 14 | /cli/report 15 | .vscode 16 | .project 17 | .classpath 18 | .settings 19 | bin 20 | /lambda/dockerstuff.sh 21 | /lambda/src/main/resources/settings.properties 22 | .DS_Store 23 | /web/unknown 24 | unknown 25 | /web-lambda/unknown 26 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/browser/CloudBrowser.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import java.io.IOException; 4 | import java.util.List; 5 | import java.util.concurrent.ExecutionException; 6 | 7 | public interface CloudBrowser { 8 | 9 | void takeScreenshots(List screenshotContexts) throws ExecutionException, InterruptedException, IOException; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'jlineup' 2 | include('core', 'lambda', 'cli', 'web', 'web-lambda', 'cli-lambda') 3 | 4 | project(":core").name = "jlineup-core" 5 | project(":lambda").name = "jlineup-lambda" 6 | project(":cli").name = "jlineup-cli" 7 | project(":web").name = "jlineup-web" 8 | project(":cli-lambda").name = "jlineup-cli-lambda" 9 | project(":web-lambda").name = "jlineup-web-lambda" 10 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome-wait_for_selectors.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test_dom.html" 6 | ], 7 | "wait-for-selectors": [ 8 | ".later" 9 | ], 10 | "fail-if-selectors-not-found": true 11 | } 12 | }, 13 | "browser" : "chrome-headless" 14 | } -------------------------------------------------------------------------------- /web/src/main/resources/META-INF/additional-spring-configuration-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": [ 3 | { 4 | "name": "jlineup.allowedUrlPrefixes", 5 | "type": "java.util.List", 6 | "description": "All URLs that you tell JLineup to call via REST api have to begin with one of these prefixes. This is a security feature to prevent JLineup from calling arbitrary URLs." 7 | } 8 | ] } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_wrong_url_firefox.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.7777444444.oco;ooe.de": { 4 | "paths": [ 5 | "/" 6 | ], 7 | "max-diff": 0.05, 8 | "resolutions": [ 9 | 600,800,1000,1200 10 | ] 11 | } 12 | }, 13 | "browser": "firefox-headless", 14 | "wait-after-page-load": 1, 15 | "threads": 4, 16 | "report-format" : 2 17 | } -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/configuration/JLineupWebLambdaProperties.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web.configuration; 2 | public class JLineupWebLambdaProperties { 3 | 4 | private String functionName = "jlineup-lambda"; 5 | 6 | public String getFunctionName() { 7 | return functionName; 8 | } 9 | 10 | public void setFunctionName(String functionName) { 11 | this.functionName = functionName; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_long_url.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html?abcdefghijklmnopqrstuvwxyz=12345678901234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890" 6 | ] 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/browser/TestSupportWebDriver.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import org.openqa.selenium.HasCapabilities; 4 | import org.openqa.selenium.JavascriptExecutor; 5 | import org.openqa.selenium.TakesScreenshot; 6 | import org.openqa.selenium.WebDriver; 7 | 8 | public interface TestSupportWebDriver extends WebDriver, JavascriptExecutor, TakesScreenshot, HasCapabilities { 9 | //Just an interface for mocking 10 | } 11 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_timeout.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.otto.de": { 4 | "paths": [ 5 | "/" 6 | ], 7 | "max-diff": 0.05, 8 | "resolutions": [ 9 | 600, 10 | 800, 11 | 1000, 12 | 1200 13 | ] 14 | } 15 | }, 16 | "browser": "chrome-headless", 17 | "wait-after-page-load": 4, 18 | "threads": 4, 19 | "report-format" : 2, 20 | "timeout": 3 21 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome-wait_for_selectors_fails_but_no_error.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test_dom.html" 6 | ], 7 | "wait-for-selectors": [ 8 | "#willNeverBeHere" 9 | ], 10 | "fail-if-selectors-not-found": false, 11 | "wait-for-selectors-timeout": 1 12 | } 13 | }, 14 | "browser" : "chrome-headless" 15 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome-wait_for_selectors_fails_with_error.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test_dom.html" 6 | ], 7 | "wait-for-selectors": [ 8 | "#willNeverBeHere" 9 | ], 10 | "fail-if-selectors-not-found": true, 11 | "wait-for-selectors-timeout": 2 12 | } 13 | }, 14 | "browser" : "chrome-headless" 15 | } -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/config/RunStep.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import de.otto.jlineup.browser.BrowserStep; 4 | 5 | public enum RunStep { 6 | before, 7 | after, 8 | after_only, 9 | compare; 10 | 11 | public BrowserStep toBrowserStep() { 12 | if (this == before) return BrowserStep.before; 13 | else if (this == compare) return BrowserStep.compare; 14 | else return BrowserStep.after; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/test/resources/lineup_test_context_before.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.otto.de": { 4 | "paths": [ 5 | "/" 6 | ], 7 | "max-diff": 0, 8 | "cookies": [ 9 | { 10 | "name": "someCookieWithAlternatingValueBetweenBeforeAndAfter", 11 | "value": "1234567890" 12 | } 13 | ] 14 | } 15 | }, 16 | "browser": "firefox", 17 | "wait-after-page-load": 1, 18 | "page-load-timeout": 60 19 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_wrong_url_chrome.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.cccbbbaaathispagedoesnotexistaaabbbccc.de": { 4 | "paths": [ 5 | "/" 6 | ], 7 | "max-diff": 0.05, 8 | "resolutions": [ 9 | 600,800,1000,1200 10 | ] 11 | } 12 | }, 13 | "browser": "chrome-headless", 14 | "wait-after-page-load": 1, 15 | "threads": 4, 16 | "check-for-errors-in-log": true, 17 | "report-format" : 2 18 | } -------------------------------------------------------------------------------- /docs/pipeline-example.mermaid: -------------------------------------------------------------------------------- 1 | graph TD 2 | Z(Previous steps of CI build) --> A 3 | subgraph Prelive stage 4 | A[JLineup 'before'] -->B[Deployment to 'prelive'] 5 | B --> C[JLineup 'after'] 6 | C -->|No changes detected| D[Open gate to live deployment] 7 | C -->|Changes detected| E[Close gate to live deployment] 8 | E --> F[Show link to JLineup HTML report] 9 | F -->|Manual approval| D 10 | end 11 | D --> X(Live deployment) 12 | F -->|No manual approval| T(Fail build) 13 | -------------------------------------------------------------------------------- /core/src/test/resources/lineup_test_context_after.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.otto.de": { 4 | "paths": [ 5 | "/" 6 | ], 7 | "max-diff": 0, 8 | "cookies": [ 9 | { 10 | "name": "someCookieWithAlternatingValueBetweenBeforeAndAfter", 11 | "value": "abcdefghijklmnopqrstuvwxyz" 12 | } 13 | ] 14 | } 15 | }, 16 | "browser": "firefox", 17 | "wait-after-page-load": 1, 18 | "page-load-timeout": 60 19 | } -------------------------------------------------------------------------------- /.run/JLineupWebApplication.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_wrong_js.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ], 7 | "max-diff": 0.05, 8 | "resolutions": [ 9 | 600,800,1000,1200 10 | ], 11 | "javascript": "console.thisfunctiondoesnotexist('Moin!');" 12 | } 13 | }, 14 | "browser": "chrome-headless", 15 | "wait-after-page-load": 1, 16 | "threads": 4, 17 | "report-format" : 2 18 | } -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/service/BrowserNotInstalledException.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.service; 2 | 3 | import de.otto.jlineup.browser.Browser; 4 | 5 | public class BrowserNotInstalledException extends Exception { 6 | 7 | private final Browser.Type desiredBrowser; 8 | 9 | public BrowserNotInstalledException(Browser.Type desiredBrowser) { 10 | this.desiredBrowser = desiredBrowser; 11 | } 12 | 13 | public Browser.Type getDesiredBrowser() { 14 | return desiredBrowser; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /web/src/test/resources/application-test.yml: -------------------------------------------------------------------------------- 1 | jlineup: 2 | installedBrowsers: chrome, chrome-headless 3 | chromeLaunchParameters: --user-data-dir=/tmp/jlineup/chrome-profile-{id}, --force-device-scale-factor=1 4 | allowedUrlPrefixes: 5 | - https://www.otto.de 6 | - https://develop.otto.de 7 | - https://www.example.com 8 | 9 | server: 10 | servlet: 11 | context-path: /jlineup-test-contextpath 12 | 13 | management: 14 | endpoints: 15 | web: 16 | exposure: 17 | include: '*' 18 | endpoint: 19 | loggers: 20 | access: unrestricted -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/browser/WebConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | @Configuration 8 | public class WebConfiguration implements WebMvcConfigurer { 9 | 10 | @Override 11 | public void configurePathMatch(PathMatchConfigurer configurer) { 12 | configurer.setUseTrailingSlashMatch(true); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/configuration/JLineupConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web.configuration; 2 | 3 | import de.otto.jlineup.service.Housekeeper; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import java.nio.file.Paths; 8 | 9 | @Configuration 10 | public class JLineupConfiguration { 11 | 12 | @Bean 13 | public Housekeeper houseKeeper(JLineupWebProperties properties) { 14 | return new Housekeeper(Paths.get(properties.getWorkingDirectory())); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/GlobalOption.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup; 2 | 3 | public enum GlobalOption { 4 | 5 | JLINEUP_LAMBDA_FUNCTION_NAME, 6 | JLINEUP_LAMBDA_AWS_PROFILE, 7 | JLINEUP_LAMBDA_S3_BUCKET, 8 | JLINEUP_CROP_LAST_SCREENSHOT, 9 | 10 | JLINEUP_CHROME_VERSION, 11 | JLINEUP_FIREFOX_VERSION; 12 | 13 | public String kebabCaseName() { 14 | return name().toLowerCase().replace("_", "-"); 15 | } 16 | 17 | public String kebabCaseNameWithoutJLineupPrefix() { 18 | return kebabCaseName().replace("jlineup-", ""); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/image/LABTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.image; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | 8 | public class LABTest { 9 | 10 | @Test 11 | public void shouldCalculateCIEDE2000() { 12 | 13 | LAB lab1 = LAB.fromRGB(55,44,33, 0); 14 | LAB lab2 = LAB.fromRGB(66,55,44, 0); 15 | 16 | double v = Math.round(LAB.ciede2000(lab1, lab2) * Math.pow(10, 12)) / Math.pow(10, 12); 17 | 18 | assertThat(v, is(3.533443206559)); 19 | } 20 | } -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17 2 | 3 | USER root 4 | RUN mkdir -p /usr/share/man/man1 5 | RUN apt-get update \ 6 | && apt-get upgrade -y \ 7 | && apt-get install -y wget \ 8 | && apt-get install -yf chromium-browser firefox libjpeg-progs \ 9 | && wget -U "jlineup-docker" -O jlineup-web.jar https://repo1.maven.org/maven2/de/otto/jlineup-web/4.14.2/jlineup-web-4.14.2.jar 10 | ADD docker/application.yml application.yml 11 | RUN apt-get remove --auto-remove perl -yf && apt-get purge --auto-remove perl -yf 12 | EXPOSE 8080 13 | 14 | ENTRYPOINT ["java","-jar","/jlineup-web.jar"] 15 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/webpage/test_dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Testpage 4 | 14 |

Hallo!

15 | -------------------------------------------------------------------------------- /cli/src/main/java/de/otto/jlineup/cli/LogToFileFilter.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.cli; 2 | 3 | import ch.qos.logback.classic.spi.ILoggingEvent; 4 | import ch.qos.logback.core.filter.Filter; 5 | import ch.qos.logback.core.spi.FilterReply; 6 | 7 | @SuppressWarnings("unused") 8 | public class LogToFileFilter extends Filter { 9 | 10 | @Override 11 | public FilterReply decide(ILoggingEvent iLoggingEvent) { 12 | if (iLoggingEvent.getMDCPropertyMap().containsKey("reportlogname")) { 13 | return FilterReply.NEUTRAL; 14 | } 15 | return FilterReply.DENY; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.run/Main (before).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | -------------------------------------------------------------------------------- /web/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | .------------------------------------------------------------------. 2 | | ___ ___ ___ ________ _______ ___ ___ ________ | 3 | | |\ \|\ \ |\ \|\ ___ \|\ ___ \ |\ \|\ \|\ __ \ | 4 | | \ \ \ \ \ \ \ \ \ \\ \ \ \ __/|\ \ \\\ \ \ \|\ \ | 5 | | __ \ \ \ \ \ \ \ \ \ \\ \ \ \ \_|/_\ \ \\\ \ \ ____\| 6 | ||\ \\_\ \ \ \____\ \ \ \ \\ \ \ \ \_|\ \ \ \\\ \ \ \___|| 7 | |\ \________\ \_______\ \__\ \__\\ \__\ \_______\ \_______\ \__\ | 8 | | \|________|\|_______|\|__|\|__| \|__|\|_______|\|_______|\|__| | 9 | '------------------------------------------------------------------' -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome_svg.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "logo.html" 6 | ], 7 | "window-widths": [ 8 | 1505, 9 | 1506, 10 | 1507, 11 | 1508, 12 | 1509, 13 | 1510, 14 | 1511, 15 | 1512, 16 | 1513, 17 | 1514, 18 | 1515 19 | ], 20 | "max-color-diff-per-pixel": 0, 21 | "wait-after-page-load": 1, 22 | "warmup-browser-cache-time": 2 23 | } 24 | }, 25 | "report-format" : 2, 26 | "threads" : 3, 27 | "browser" : "chrome-headless" 28 | } -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome_progressive_jpg.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "progressive_jpeg.html" 6 | ], 7 | "window-widths": [ 8 | 1505, 9 | 1506, 10 | 1507, 11 | 1508, 12 | 1509, 13 | 1510, 14 | 1511, 15 | 1512, 16 | 1513, 17 | 1514, 18 | 1515 19 | ], 20 | "max-color-diff-per-pixel": 0, 21 | "wait-after-page-load": 1, 22 | "warmup-browser-cache-time": 2 23 | } 24 | }, 25 | "report-format" : 2, 26 | "threads" : 3, 27 | "browser" : "chrome-headless" 28 | } -------------------------------------------------------------------------------- /.run/Main (compare).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.run/Main (print-config).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /web/src/test/java/de/otto/jlineup/utils/RegexMatcher.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.utils; 2 | 3 | import org.hamcrest.BaseMatcher; 4 | import org.hamcrest.Description; 5 | 6 | public class RegexMatcher extends BaseMatcher { 7 | private final String regex; 8 | 9 | public RegexMatcher(String regex) { 10 | this.regex = regex; 11 | } 12 | 13 | public boolean matches(Object o) { 14 | return ((String)o).matches(regex); 15 | } 16 | 17 | public void describeTo(Description description) { 18 | description.appendText("matches regex=").appendText(regex); 19 | } 20 | 21 | public static RegexMatcher regex(String regex) { 22 | return new RegexMatcher(regex); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /cli/graalvm/lineup_chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls" : { 3 | "http://www.otto.de" : { 4 | "paths" : [ "" ], 5 | "max-diff" : 0.0, 6 | "window-widths" : [ 800 ], 7 | "max-scroll-height" : 100000, 8 | "wait-after-page-load" : 0.0, 9 | "wait-after-scroll" : 0.0, 10 | "ignore-anti-aliasing" : false, 11 | "strict-color-comparison" : false, 12 | "max-color-distance" : 2.3 13 | } 14 | }, 15 | "browser" : "Chrome", 16 | "name" : "Default", 17 | "page-load-timeout" : 120, 18 | "window-height" : 800, 19 | "debug" : false, 20 | "check-for-errors-in-log" : false, 21 | "wait-after-page-load" : 0.0, 22 | "http-check": { 23 | "enabled": true 24 | }, 25 | "timeout" : 600 26 | } 27 | -------------------------------------------------------------------------------- /.run/Main (help).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /cli/graalvm/lineup_chrome_headless.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls" : { 3 | "http://www.otto.de" : { 4 | "paths" : [ "" ], 5 | "max-diff" : 0.0, 6 | "window-widths" : [ 800 ], 7 | "max-scroll-height" : 100000, 8 | "wait-after-page-load" : 0.0, 9 | "wait-after-scroll" : 0.0, 10 | "ignore-anti-aliasing" : false, 11 | "strict-color-comparison" : false, 12 | "max-color-distance" : 2.3 13 | } 14 | }, 15 | "browser" : "Chrome_Headless", 16 | "name" : "Default", 17 | "page-load-timeout" : 120, 18 | "window-height" : 800, 19 | "debug" : false, 20 | "check-for-errors-in-log" : false, 21 | "wait-after-page-load" : 0.0, 22 | "http-check": { 23 | "enabled": true 24 | }, 25 | "timeout" : 600 26 | } 27 | -------------------------------------------------------------------------------- /.run/Main (example).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /cli/graalvm/lineup_firefox_headless.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls" : { 3 | "http://www.otto.de" : { 4 | "paths" : [ "" ], 5 | "max-diff" : 0.0, 6 | "window-widths" : [ 800 ], 7 | "max-scroll-height" : 100000, 8 | "wait-after-page-load" : 0.0, 9 | "wait-after-scroll" : 0.0, 10 | "ignore-anti-aliasing" : false, 11 | "strict-color-comparison" : false, 12 | "max-color-distance" : 2.3 13 | } 14 | }, 15 | "browser" : "Firefox_Headless", 16 | "name" : "Default", 17 | "page-load-timeout" : 120, 18 | "window-height" : 800, 19 | "debug" : false, 20 | "check-for-errors-in-log" : false, 21 | "wait-after-page-load" : 0.0, 22 | "http-check": { 23 | "enabled": true 24 | }, 25 | "timeout" : 600 26 | } 27 | -------------------------------------------------------------------------------- /web/src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 |
11 |
12 |
13 |

Error page

14 |
15 |
16 |

Ooooops, the requested page is not here.

17 |

Take me to the status page!

18 |
19 |
20 |
21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.run/Main (after_only).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.run/Main (before keep existing).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.run/Main (before merge) .run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.run/Main (after, keep existing).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /cli/graalvm/java.security.overrides: -------------------------------------------------------------------------------- 1 | # 2 | # List of providers and their preference orders: 3 | # 4 | 5 | security.provider.1=sun.security.provider.Sun 6 | security.provider.2=sun.security.rsa.SunRsaSign 7 | security.provider.3=org.bouncycastle.jce.provider.BouncyCastleProvider 8 | 9 | # 10 | # Removing this to make Graal Native Image work without libsunec.so: 11 | # security.provider.3=sun.security.ec.SunEC 12 | # 13 | 14 | security.provider.4=com.sun.net.ssl.internal.ssl.Provider 15 | security.provider.5=com.sun.crypto.provider.SunJCE 16 | security.provider.6=sun.security.jgss.SunProvider 17 | security.provider.7=com.sun.security.sasl.Provider 18 | security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI 19 | security.provider.9=sun.security.smartcardio.SunPCSC 20 | #security.provider.10=sun.security.ec.SunEC -------------------------------------------------------------------------------- /.run/Main (merge print-config).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /.run/Main (before keep existing refresh url).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/service/InvalidRunStateException.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.service; 2 | 3 | import de.otto.jlineup.web.State; 4 | 5 | public class InvalidRunStateException extends Exception { 6 | 7 | private final String id; 8 | private final State currentState; 9 | private final State expectedState; 10 | 11 | public InvalidRunStateException(String id, State currentState, State expectedState) { 12 | this.id = id; 13 | this.currentState = currentState; 14 | this.expectedState = expectedState; 15 | } 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | 21 | public State getCurrentState() { 22 | return currentState; 23 | } 24 | 25 | public State getExpectedState() { 26 | return expectedState; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.run/Main (after).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | jlineup: 2 | workingDirectory: /tmp/jlineup/ 3 | screenshotsDirectory: report-{id} 4 | reportDirectory: report-{id} 5 | maxParallelJobs: 1 6 | chromeLaunchParameters: --use-spdy=off, --disable-dev-shm-usage 7 | cleanupProfile: true 8 | lambda: 9 | functionName: jlineup-lambda 10 | 11 | edison: 12 | application: 13 | title: JLineup 14 | description: Takes screenshots of webpages and checks for differences 15 | 16 | management: 17 | endpoint: 18 | metrics: 19 | access: unrestricted 20 | prometheus: 21 | access: unrestricted 22 | loggers: 23 | access: unrestricted 24 | 25 | endpoints: 26 | web: 27 | exposure: 28 | include: '*' 29 | 30 | spring: 31 | application: 32 | name: JLineup 33 | banner: 34 | location: classpath:banner.txt 35 | 36 | server: 37 | error: 38 | whitelabel: 39 | enabled: false -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/Report.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import de.otto.jlineup.config.JobConfig; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.stream.Collectors; 8 | 9 | public class Report { 10 | 11 | public final Summary summary; 12 | public final Map screenshotComparisonsForUrl; 13 | public final JobConfig config; 14 | 15 | public Report(Summary summary, Map screenshotComparisons, JobConfig config) { 16 | this.summary = summary; 17 | this.screenshotComparisonsForUrl = screenshotComparisons; 18 | this.config = config.sanitize(); 19 | } 20 | 21 | public List getFlatResultList() { 22 | return screenshotComparisonsForUrl.values().stream().flatMap(urlReport -> urlReport.comparisonResults.stream()).collect(Collectors.toList()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/JSONReportWriter_V2.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import de.otto.jlineup.JacksonWrapper; 5 | import de.otto.jlineup.file.FileService; 6 | 7 | import java.io.FileNotFoundException; 8 | 9 | public class JSONReportWriter_V2 implements JSONReportWriter { 10 | 11 | private final static PropertyNamingStrategy NAMING_STRATEGY = PropertyNamingStrategy.KEBAB_CASE; 12 | 13 | private final FileService fileService; 14 | 15 | public JSONReportWriter_V2(FileService fileService) { 16 | this.fileService = fileService; 17 | } 18 | 19 | public void writeComparisonReportAsJson(Report report) throws FileNotFoundException { 20 | final String reportJson = JacksonWrapper.serializeObjectWithPropertyNamingStrategy(report, NAMING_STRATEGY); 21 | fileService.writeJsonReport(reportJson); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/JSONReportWriter_V1.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import de.otto.jlineup.JacksonWrapper; 5 | import de.otto.jlineup.file.FileService; 6 | 7 | import java.io.FileNotFoundException; 8 | 9 | public class JSONReportWriter_V1 implements JSONReportWriter { 10 | 11 | private final static PropertyNamingStrategy NAMING_STRATEGY = PropertyNamingStrategy.LOWER_CAMEL_CASE; 12 | 13 | private final FileService fileService; 14 | 15 | public JSONReportWriter_V1(FileService fileService) { 16 | this.fileService = fileService; 17 | } 18 | 19 | public void writeComparisonReportAsJson(Report report) throws FileNotFoundException { 20 | final String reportJson = JacksonWrapper.serializeObjectWithPropertyNamingStrategy(report.getFlatResultList(), NAMING_STRATEGY); 21 | fileService.writeJsonReport(reportJson); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web/docker/application.yml: -------------------------------------------------------------------------------- 1 | env: ${STAGE:local} 2 | 3 | jlineup: 4 | workingDirectory: /tmp/jlineup/ 5 | screenshotsDirectory: report-{id} 6 | reportDirectory: report-{id} 7 | installedBrowsers: chrome-headless, firefox-headless 8 | maxParallelJobs: 2 9 | maxThreadsPerJob: 6 10 | chromeLaunchParameters: --use-spdy=off,--disable-dev-shm-usage 11 | 12 | management: 13 | context-path: /internal 14 | security: 15 | enabled: false 16 | 17 | server: 18 | servlet: 19 | context-path: / 20 | port: ${PORT:8080} 21 | use-forward-headers: true 22 | 23 | spring: 24 | application: 25 | name: JLineup 26 | jackson: 27 | serialization: 28 | INDENT_OUTPUT: true 29 | thymeleaf: 30 | mode: HTML 31 | 32 | edison: 33 | application: 34 | environment: ${STAGE:local} 35 | group: ${GROUP:none} 36 | title: JLineup [${STAGE:local}] 37 | status: 38 | team: 39 | business-contact: ${BUSINESSMAIL:none} 40 | name: ${TEAMNAME:none} 41 | technical-contact: ${TECHMAIL:none} 42 | -------------------------------------------------------------------------------- /cli/src/test/resources/acceptance/acceptance_chrome_devices.lineup.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "file://###CWD###/src/test/resources/acceptance/webpage/": { 4 | "paths": [ 5 | "test.html" 6 | ], 7 | "devices": [ 8 | { 9 | "device-name": "iPhone 14 Pro Max" 10 | }, 11 | { 12 | "deviceName": "MOBILE", 13 | "userAgent": "mobile1", 14 | "width": 600, 15 | "height": 1200, 16 | "pixelRatio": 2.5, 17 | "touch": true 18 | }, 19 | { 20 | "deviceName": "MOBILE", 21 | "userAgent": "mobile2", 22 | "width": 320, 23 | "height": 480, 24 | "pixelRatio": 1, 25 | "touch": true 26 | }, 27 | { 28 | "deviceName": "DESKTOP", 29 | "width": 1000, 30 | "height": 1000 31 | } 32 | ] 33 | } 34 | }, 35 | "report-format" : 2, 36 | "wait-after-page-load" : 1, 37 | "threads" : 1, 38 | "browser" : "chrome-headless" 39 | } -------------------------------------------------------------------------------- /cli/graalvm/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources":{ 3 | "includes":[ 4 | { 5 | "pattern":"\\Qlogback.xml\\E" 6 | }, 7 | { 8 | "pattern":"\\Qmozilla/public-suffix-list.txt\\E" 9 | }, 10 | { 11 | "pattern":"\\Qorg/apache/hc/client5/version.properties\\E" 12 | }, 13 | { 14 | "pattern":"\\Qorg/openqa/selenium/firefox/webdriver_prefs.json\\E" 15 | }, 16 | { 17 | "pattern":"\\Qorg/slf4j/impl/StaticLoggerBinder.class\\E" 18 | }, 19 | { 20 | "pattern":"\\Qorg/thymeleaf/thymeleaf.properties\\E" 21 | }, 22 | { 23 | "pattern":"\\Qtemplates/report.html\\E" 24 | }, 25 | { 26 | "pattern":"\\Qtemplates/report_not_finished.html\\E" 27 | }, 28 | { 29 | "pattern":"\\Qtemplates/report_legacy.html\\E" 30 | }, 31 | { 32 | "pattern":"\\Qversion.properties\\E" 33 | }, 34 | { 35 | "pattern":"\\Qwebdrivermanager.properties\\E" 36 | } 37 | ]}, 38 | "bundles":[{ 39 | "name":"sun.awt.resources.awt" 40 | }] 41 | } 42 | -------------------------------------------------------------------------------- /web-lambda/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.springframework.boot' 2 | 3 | dependencies { 4 | runtimeOnly(project(":jlineup-lambda")) { 5 | artifact { 6 | name = "jlineup-lambda" 7 | type = "jar" 8 | classifier = "plain" 9 | } 10 | } 11 | runtimeOnly(project(":jlineup-web")) { 12 | artifact { 13 | name = "jlineup-web" 14 | type = "jar" 15 | classifier = "plain" 16 | } 17 | } 18 | } 19 | 20 | application { 21 | mainClass.set("de.otto.jlineup.web.JLineupWebApplication") 22 | } 23 | 24 | bootJar { 25 | mainClass = "de.otto.jlineup.web.JLineupWebApplication" 26 | } 27 | 28 | publishing { 29 | publications { 30 | mavenJava(MavenPublication) { 31 | groupId = "de.otto" 32 | artifactId = 'jlineup-web-lambda' 33 | from components.java 34 | pom { 35 | name = 'JLineup Web Lambda' 36 | description = 'The web variant of JLineup with AWS Lambda support' 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/State.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web; 2 | 3 | public enum State { 4 | BEFORE_PENDING("'before' pending"), 5 | BEFORE_RUNNING("'before' running"), 6 | BEFORE_DONE("'before' done"), 7 | AFTER_PENDING("'after' pending"), 8 | AFTER_RUNNING("'after' running"), 9 | FINISHED_WITHOUT_DIFFERENCES("finished without differences"), 10 | FINISHED_WITH_DIFFERENCES("finished with differences"), 11 | ERROR("error"), 12 | DEAD("dead"); 13 | 14 | private final String humanReadableName; 15 | 16 | State(String humanReadableName) { 17 | this.humanReadableName = humanReadableName; 18 | } 19 | 20 | public String getHumanReadableName() { 21 | return humanReadableName; 22 | } 23 | 24 | public boolean isDone() { 25 | return this == FINISHED_WITH_DIFFERENCES || this == FINISHED_WITHOUT_DIFFERENCES || this == ERROR || this == DEAD; 26 | } 27 | 28 | public boolean isNonPersistable() { 29 | return this == BEFORE_PENDING || this == BEFORE_RUNNING || this == AFTER_PENDING || this == AFTER_RUNNING; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/JLineupRunnerTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup; 2 | 3 | import de.otto.jlineup.config.JobConfig; 4 | import de.otto.jlineup.config.UrlConfig; 5 | import de.otto.jlineup.report.Summary; 6 | import de.otto.jlineup.report.UrlReport; 7 | import org.junit.Test; 8 | 9 | import java.util.Collections; 10 | import java.util.Map; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | 15 | public class JLineupRunnerTest { 16 | 17 | @Test 18 | public void shouldNotFailIfMaxDiffIsSameAsDetectedDiff() { 19 | 20 | UrlReport urlReport = new UrlReport(null, new Summary(false, 0, 15.1234567890123456, 0)); 21 | boolean detectedDifferenceGreaterThanMaxDifference = JLineupRunner.isDetectedDifferenceGreaterThanMaxDifference(Collections.singleton(Map.entry("abc", urlReport)), JobConfig.exampleConfigBuilder().withUrls(Collections.singletonMap("abc", UrlConfig.urlConfigBuilder().withMaxDiff(15.1234567890123456).build())).build()); 22 | 23 | assertThat(detectedDifferenceGreaterThanMaxDifference, is(false)); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /cli/src/main/java/de/otto/jlineup/cli/Main.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.cli; 2 | 3 | import de.otto.jlineup.Utils; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import picocli.CommandLine; 7 | 8 | import static java.lang.invoke.MethodHandles.lookup; 9 | 10 | public class Main { 11 | 12 | private final static Logger LOG = LoggerFactory.getLogger(lookup().lookupClass()); 13 | static final int NO_EXIT = -1; 14 | 15 | public static void main(String[] args) { 16 | 17 | //For GraalVM support in WebdriverManager 18 | String arch = System.getProperty("os.arch"); 19 | if (arch.endsWith("64") && "Substrate VM".equals(System.getProperty("java.vm.name"))) { 20 | System.setProperty("wdm.architecture", "X64"); 21 | } 22 | 23 | Utils.setDebugLogLevelsOfSelectedThirdPartyLibsToWarn(); 24 | 25 | int exitCode = new CommandLine(new JLineup()).execute(args); 26 | exitWithExitCode(exitCode); 27 | } 28 | 29 | private static void exitWithExitCode(int exitCode) { 30 | Utils.stopFileLoggers(); 31 | if (exitCode != NO_EXIT) { 32 | System.exit(exitCode); 33 | } 34 | } 35 | 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/UrlReport.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public class UrlReport { 7 | 8 | public final List comparisonResults; 9 | public final Summary summary; 10 | 11 | public UrlReport(List comparisonResults, Summary summary) { 12 | this.comparisonResults = comparisonResults; 13 | this.summary = summary; 14 | } 15 | 16 | @Override 17 | public boolean equals(Object o) { 18 | if (this == o) return true; 19 | if (o == null || getClass() != o.getClass()) return false; 20 | UrlReport urlReport = (UrlReport) o; 21 | return Objects.equals(comparisonResults, urlReport.comparisonResults) && Objects.equals(summary, urlReport.summary); 22 | } 23 | 24 | @Override 25 | public int hashCode() { 26 | return Objects.hash(comparisonResults, summary); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "UrlReport{" + 32 | "comparisonResults=" + comparisonResults + 33 | ", summary=" + summary + 34 | '}'; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/config/ConfigMergerTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static de.otto.jlineup.config.JobConfig.EXAMPLE_URL; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.Matchers.is; 8 | 9 | class ConfigMergerTest { 10 | 11 | @Test 12 | void shouldMergeIdenticalExampleConfigs() { 13 | 14 | //Given 15 | JobConfig jobConfig = JobConfig.exampleConfig(); 16 | JobConfig mergeConfig = JobConfig.exampleConfig(); 17 | 18 | UrlConfig urlConfig = jobConfig.urls.get(EXAMPLE_URL); 19 | //Javascript is merged by concatenating with ";" 20 | UrlConfig expectedUrlConfig = UrlConfig.copyOfBuilder(urlConfig).withJavaScript(urlConfig.javaScript + ";" + urlConfig.javaScript).build(); 21 | JobConfig expectedConfig = JobConfig.copyOfBuilder(jobConfig).withUrls(null).addUrlConfig(expectedUrlConfig.url, expectedUrlConfig).build(); 22 | 23 | //When 24 | JobConfig resultingConfig = ConfigMerger.mergeJobConfigWithMergeConfig(jobConfig, mergeConfig).insertDefaults(); 25 | 26 | //Then 27 | assertThat(resultingConfig, is(expectedConfig)); 28 | 29 | } 30 | } -------------------------------------------------------------------------------- /lambda/src/main/java/de/otto/jlineup/lambda/LambdaRequestPayload.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.lambda; 2 | 3 | import de.otto.jlineup.browser.ScreenshotContext; 4 | import de.otto.jlineup.config.JobConfig; 5 | import de.otto.jlineup.config.RunStep; 6 | 7 | public class LambdaRequestPayload { 8 | 9 | public final String runId; 10 | public final JobConfig jobConfig; 11 | public final ScreenshotContext screenshotContext; 12 | 13 | //This is here additionally because it has JsonIgnore annotation in ScreenshotContext 14 | public final String urlKey; 15 | 16 | //Also step is not in ScreenshotContext because of to JsonIgnore 17 | public final RunStep step; 18 | 19 | 20 | public LambdaRequestPayload(String runId, JobConfig jobConfig, ScreenshotContext screenshotContext, RunStep step, String urlKey) { 21 | this.runId = runId; 22 | this.jobConfig = jobConfig; 23 | this.screenshotContext = screenshotContext; 24 | this.urlKey = urlKey; 25 | this.step = step; 26 | } 27 | 28 | public LambdaRequestPayload() { 29 | runId = null; 30 | jobConfig = null; 31 | screenshotContext = null; 32 | urlKey = null; 33 | step = null; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/JLineupWebApplication.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web; 2 | 3 | import de.otto.jlineup.Utils; 4 | import de.otto.jlineup.web.configuration.JLineupWebProperties; 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.boot.builder.SpringApplicationBuilder; 9 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 10 | import org.springframework.context.annotation.PropertySource; 11 | 12 | import static java.lang.invoke.MethodHandles.lookup; 13 | 14 | @PropertySource(value = "version.properties", ignoreResourceNotFound = true) 15 | @SpringBootApplication(scanBasePackages = {"de.otto.jlineup", "de.otto.edison"}) 16 | @EnableConfigurationProperties(JLineupWebProperties.class) 17 | public class JLineupWebApplication { 18 | 19 | private final static Logger LOG = LoggerFactory.getLogger(lookup().lookupClass()); 20 | 21 | public static void main(String[] args) { 22 | LOG.info("\nStarting JLineup {}\n", Utils.getVersion()); 23 | SpringApplicationBuilder builder = new SpringApplicationBuilder(JLineupWebApplication.class); 24 | builder.headless(false).run(args); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/configuration/NavigationConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web.configuration; 2 | 3 | import de.otto.edison.configuration.EdisonApplicationProperties; 4 | import de.otto.edison.navigation.NavBar; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | import org.springframework.stereotype.Component; 8 | 9 | import static de.otto.edison.navigation.NavBarItem.bottom; 10 | import static de.otto.edison.navigation.NavBarItem.navBarItem; 11 | 12 | @Component 13 | @EnableConfigurationProperties(EdisonApplicationProperties.class) 14 | public class NavigationConfiguration { 15 | 16 | @Autowired 17 | public NavigationConfiguration(final NavBar mainNavBar, final NavBar rightNavBar, 18 | final EdisonApplicationProperties properties) { 19 | mainNavBar.register(navBarItem(0, "Status", String.format("%s/status", properties.getManagement().getBasePath()))); 20 | mainNavBar.register(navBarItem(1, "Reports", String.format("%s/reports", properties.getManagement().getBasePath()))); 21 | 22 | rightNavBar.register(navBarItem(bottom(), "Example run", "/exampleRun")); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/file/FileUtils.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.file; 2 | 3 | import java.io.IOException; 4 | import java.io.UncheckedIOException; 5 | import java.nio.file.DirectoryStream; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | 10 | public class FileUtils { 11 | 12 | 13 | public static void clearDirectory(String path) throws IOException { 14 | Path directory = Paths.get(path); 15 | try(DirectoryStream paths = Files.newDirectoryStream(directory);) { 16 | paths.forEach(file -> { 17 | try { 18 | if (Files.isDirectory(file)) { 19 | clearDirectory(file.toAbsolutePath().toString()); 20 | } 21 | 22 | Files.delete(file); 23 | } catch (IOException e) { 24 | throw new UncheckedIOException(e); 25 | } 26 | }); 27 | } 28 | } 29 | 30 | public static void deleteDirectory(Path path) throws IOException { 31 | clearDirectory(path.toString()); 32 | Files.deleteIfExists(path); 33 | } 34 | 35 | public static void deleteDirectory(String path) throws IOException { 36 | deleteDirectory(Paths.get(path)); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /cli-lambda/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.springframework.boot' 2 | 3 | dependencies { 4 | runtimeOnly(project(":jlineup-lambda")) { 5 | artifact { 6 | name = "jlineup-lambda" 7 | type = "jar" 8 | classifier = "plain" 9 | } 10 | } 11 | runtimeOnly(project(":jlineup-cli")) { 12 | artifact { 13 | name = "jlineup-cli" 14 | type = "jar" 15 | classifier = "plain" 16 | } 17 | } 18 | } 19 | 20 | tasks.bootDistZip { 21 | archiveBaseName = project.name 22 | } 23 | 24 | tasks.bootDistTar { 25 | archiveBaseName = project.name 26 | } 27 | 28 | test { 29 | testLogging { 30 | exceptionFormat = 'full' 31 | showStandardStreams = true 32 | } 33 | } 34 | 35 | jar { 36 | enabled = false 37 | } 38 | 39 | bootJar { 40 | mainClass = "de.otto.jlineup.cli.Main" 41 | } 42 | 43 | application { 44 | mainClass.set("de.otto.jlineup.cli.Main") 45 | } 46 | 47 | publishing { 48 | publications { 49 | mavenJava(MavenPublication) { 50 | groupId = "de.otto" 51 | artifactId = 'jlineup-cli-lambda' 52 | from components.java 53 | 54 | pom { 55 | name = 'JLineup CLI Lambda' 56 | description = 'The cli AWS Lambda variant of JLineup' 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /cli/src/test/java/de/otto/jlineup/cli/RunRunStepConfigTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.cli; 2 | 3 | import de.otto.jlineup.RunStepConfig; 4 | import de.otto.jlineup.config.RunStep; 5 | import org.junit.Test; 6 | import picocli.CommandLine; 7 | 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | import static org.hamcrest.core.Is.is; 10 | 11 | public class RunRunStepConfigTest { 12 | 13 | @Test 14 | public void shouldConvertCommandlineParameters() { 15 | 16 | JLineup commandLineParameters = new JLineup(); 17 | String[] params = { 18 | "--screenshot-dir", "someScreenshotDirectory", 19 | "--report-dir", "someReportDirectory", 20 | "--working-dir", "someWorkingDirectory", 21 | "--step", "after" 22 | }; 23 | CommandLine commandLine = new CommandLine(commandLineParameters); 24 | commandLine.parseArgs(params); 25 | 26 | RunStepConfig runStepConfig = Utils.convertCommandLineParametersToRunConfiguration(commandLineParameters); 27 | 28 | assertThat(runStepConfig.getReportDirectory(), is("someReportDirectory")); 29 | assertThat(runStepConfig.getScreenshotsDirectory(), is("someScreenshotDirectory")); 30 | assertThat(runStepConfig.getWorkingDirectory(), is("someWorkingDirectory")); 31 | assertThat(runStepConfig.getStep(), is(RunStep.after)); 32 | } 33 | } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | # Check for updates to GitHub Actions every week 12 | interval: "weekly" 13 | - package-ecosystem: "gradle" # See documentation for possible values 14 | directory: "/" # Location of package manifests 15 | schedule: 16 | interval: "daily" 17 | open-pull-requests-limit: 25 18 | ignore: 19 | - dependency-name: "org.apache.tomcat.embed:*" 20 | update-types: [ "version-update:semver-major" ] 21 | cooldown: 22 | semver-major-days: 7 23 | semver-minor-days: 7 24 | semver-patch-days: 7 25 | include: 26 | - "com.amazonaws:*" 27 | - "software.amazon.awssdk:*" 28 | - "software.amazon.awssdk.crt:*" 29 | groups: 30 | aws-dependencies: 31 | patterns: 32 | - "com.amazonaws:*" 33 | - "software.amazon.awssdk:*" 34 | - "software.amazon.awssdk.crt:*" 35 | test-dependencies: 36 | patterns: 37 | - "org.junit.*" 38 | - "junit:junit:*" 39 | - "org.mockito.*" 40 | - "org.hamcrest.*" 41 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/browser/CloudBrowserFactory.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import de.otto.jlineup.RunStepConfig; 4 | import de.otto.jlineup.config.JobConfig; 5 | import de.otto.jlineup.file.FileService; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.lang.reflect.Constructor; 10 | import java.lang.reflect.InvocationTargetException; 11 | 12 | import static java.lang.invoke.MethodHandles.lookup; 13 | 14 | public class CloudBrowserFactory { 15 | 16 | private final static Logger LOG = LoggerFactory.getLogger(lookup().lookupClass()); 17 | 18 | public static CloudBrowser createCloudBrowser(RunStepConfig runStepConfig, JobConfig jobConfig, FileService fileService) throws ClassNotFoundException { 19 | try { 20 | Class lambdaBrowserClass = Class.forName("de.otto.jlineup.lambda.LambdaBrowser", false, CloudBrowserFactory.class.getClassLoader()); 21 | Constructor lambdaBrowserConstructor = lambdaBrowserClass.getConstructor(RunStepConfig.class, JobConfig.class, FileService.class); 22 | return (CloudBrowser) lambdaBrowserConstructor.newInstance(runStepConfig, jobConfig, fileService); 23 | } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) { 24 | LOG.debug("No LambdaBrowser reachable.", e); 25 | } 26 | throw new ClassNotFoundException("Using a locally installed browser."); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/UrlReportV2.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public class UrlReportV2 { 7 | 8 | public final String urlKey; 9 | public final String url; 10 | public final Summary summary; 11 | public final List contextReports; 12 | 13 | public UrlReportV2(String urlKey, String url, Summary summary, List contextReports) { 14 | this.urlKey = urlKey; 15 | this.url = url; 16 | this.summary = summary; 17 | this.contextReports = contextReports; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "UrlReportV2{" + 23 | "urlKey='" + urlKey + '\'' + 24 | ", url='" + url + '\'' + 25 | ", summary=" + summary + 26 | ", contextReports=" + contextReports + 27 | '}'; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | UrlReportV2 that = (UrlReportV2) o; 35 | return Objects.equals(urlKey, that.urlKey) && Objects.equals(url, that.url) && Objects.equals(summary, that.summary) && Objects.equals(contextReports, that.contextReports); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(urlKey, url, summary, contextReports); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /web/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | reportlogname 7 | unknown 8 | 9 | 10 | 11 | ${reportlogname} 12 | 13 | %d{HH:mm:ss:SSS} | %-5level | %thread | %logger{20} | %msg%n%rEx 14 | 15 | 16 | 17 | 18 | 19 | return ((String)mdc.get("reportlogname")) == null; 20 | 21 | NEUTRAL 22 | DENY 23 | 24 | 25 | 26 | 27 | 28 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/file/FileUtilsTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.file; 2 | 3 | import org.junit.Test; 4 | 5 | import java.io.IOException; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | 10 | import static de.otto.jlineup.file.FileUtils.clearDirectory; 11 | import static de.otto.jlineup.file.FileUtils.deleteDirectory; 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | 15 | public class FileUtilsTest { 16 | 17 | 18 | @Test 19 | public void shouldClearDirectory() throws IOException { 20 | //given 21 | final Path dirToClear = Files.createTempDirectory("jlineup-fileutils-test"); 22 | Files.createFile(dirToClear.resolve(Paths.get("test1"))); 23 | Files.createFile(dirToClear.resolve(Paths.get("test2"))); 24 | Files.createFile(dirToClear.resolve(Paths.get("test3"))); 25 | 26 | //when 27 | clearDirectory(dirToClear.toString()); 28 | 29 | //then 30 | assertThat(dirToClear.toFile().list().length, is(0)); 31 | 32 | Files.delete(dirToClear); 33 | } 34 | 35 | @Test 36 | public void shouldDeleteDirectory() throws IOException { 37 | //given 38 | final Path dirToDelete = Files.createTempDirectory("jlineup-fileutils-test"); 39 | Files.createDirectories(dirToDelete.resolve("one/two/three")); 40 | 41 | //when 42 | deleteDirectory(dirToDelete); 43 | 44 | //then 45 | assertThat(Files.exists(dirToDelete), is(false)); 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/Summary.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import java.util.Objects; 4 | 5 | public class Summary { 6 | 7 | public final boolean error; 8 | public final double differenceSum; 9 | public final double differenceMax; 10 | public final int acceptedDifferentPixels; 11 | 12 | public Summary(boolean error, double difference, double differenceMax, int acceptedDifferentPixels) { 13 | this.error = error; 14 | this.differenceSum = difference; 15 | this.differenceMax = differenceMax; 16 | this.acceptedDifferentPixels = acceptedDifferentPixels; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) return true; 22 | if (o == null || getClass() != o.getClass()) return false; 23 | Summary summary = (Summary) o; 24 | return error == summary.error && Double.compare(summary.differenceSum, differenceSum) == 0 && Double.compare(summary.differenceMax, differenceMax) == 0 && acceptedDifferentPixels == summary.acceptedDifferentPixels; 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return Objects.hash(error, differenceSum, differenceMax, acceptedDifferentPixels); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Summary{" + 35 | "error=" + error + 36 | ", differenceSum=" + differenceSum + 37 | ", differenceMax=" + differenceMax + 38 | ", acceptedDifferentPixels=" + acceptedDifferentPixels + 39 | '}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/config/CustomDateDeserializer.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.databind.DeserializationContext; 5 | import com.fasterxml.jackson.databind.JsonDeserializer; 6 | 7 | import java.io.IOException; 8 | import java.text.ParseException; 9 | import java.text.SimpleDateFormat; 10 | import java.util.Date; 11 | import java.util.Locale; 12 | 13 | public class CustomDateDeserializer extends JsonDeserializer { 14 | 15 | private static final String COOKIE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssXXX"; 16 | private static final String COOKIE_TIME_FORMAT_3 = "yyyy-MM-dd'T'HH:mm:ssZ"; 17 | private static final String COOKIE_TIME_FORMAT_2 = "yyyy-MM-dd"; 18 | 19 | private static final String[] DATE_FORMATS = new String[] { 20 | COOKIE_TIME_FORMAT, 21 | COOKIE_TIME_FORMAT_2, 22 | COOKIE_TIME_FORMAT_3 23 | }; 24 | 25 | @Override 26 | public Date deserialize(JsonParser paramJsonParser, DeserializationContext paramDeserializationContext) 27 | throws IOException { 28 | if (paramJsonParser == null || "".equals(paramJsonParser.getText())) 29 | return null; 30 | String date = paramJsonParser.getText(); 31 | 32 | for (String format : DATE_FORMATS) { 33 | try { 34 | return new SimpleDateFormat(format, Locale.US).parse(date); 35 | } catch (ParseException e) { 36 | //This page was left blank intentionally 37 | } 38 | } 39 | throw new IOException("Could not parse date '" + date + "'"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/ReportGenerator.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import de.otto.jlineup.config.JobConfig; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.OptionalDouble; 9 | import java.util.stream.Collectors; 10 | 11 | public class ReportGenerator { 12 | 13 | public Report generateReport(Map> screenshotComparisonResultLists, JobConfig config) { 14 | List resultList = screenshotComparisonResultLists.values().stream().flatMap(List::stream).collect(Collectors.toList()); 15 | final Summary summary = getSummary(resultList); 16 | 17 | HashMap urlReports = new HashMap<>(); 18 | for (Map.Entry> result : screenshotComparisonResultLists.entrySet()) { 19 | Summary localSummary = getSummary(result.getValue()); 20 | UrlReport urlReport = new UrlReport(result.getValue(), localSummary); 21 | urlReports.put(result.getKey(), urlReport); 22 | } 23 | return new Report(summary, urlReports, config); 24 | } 25 | 26 | private Summary getSummary(List resultList) { 27 | final double differenceSum = resultList.stream().mapToDouble(scr -> scr.difference).sum(); 28 | final OptionalDouble differenceMax = resultList.stream().mapToDouble(scr -> scr.difference).max(); 29 | final int acceptedDifferentPixelsSum = resultList.stream().mapToInt(scr -> scr.acceptedDifferentPixels).sum(); 30 | return new Summary(differenceSum > 0, differenceSum, differenceMax.orElseGet(() -> 0), acceptedDifferentPixelsSum); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cli/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | apply plugin: 'org.springframework.boot' 6 | //apply plugin: 'com.github.johnrengelman.shadow' 7 | 8 | dependencies { 9 | implementation project(":jlineup-core") 10 | implementation "info.picocli:picocli:${picoCliVersion}" 11 | annotationProcessor "info.picocli:picocli-codegen:${picoCliVersion}" 12 | compileOnly "org.graalvm.nativeimage:svm:${graalvmVersion}" 13 | 14 | testRuntimeOnly "org.junit.platform:junit-platform-launcher" 15 | testRuntimeOnly "org.junit.vintage:junit-vintage-engine" 16 | testCompileOnly "junit:junit" 17 | testImplementation "org.hamcrest:hamcrest" 18 | testImplementation "org.junit.jupiter:junit-jupiter" 19 | testImplementation "org.mockito:mockito-core" 20 | testImplementation("com.github.stefanbirkner:system-rules:1.19.0") { 21 | exclude group: 'junit', module: 'junit-dep' 22 | } 23 | } 24 | 25 | project.afterEvaluate { 26 | tasks.withType(JavaCompile) { 27 | def version = sourceCompatibility 28 | project.logger.info("Configuring $name to use --release $version") 29 | println("Configuring $name to use --release $version") 30 | options.compilerArgs.addAll(['--release', version]) 31 | } 32 | } 33 | 34 | bootJar { 35 | mainClass = "de.otto.jlineup.cli.Main" 36 | } 37 | 38 | application { 39 | mainClass.set("de.otto.jlineup.cli.Main") 40 | } 41 | 42 | publishing { 43 | publications { 44 | mavenJava(MavenPublication) { 45 | groupId = "de.otto" 46 | artifactId = 'jlineup-cli' 47 | from components.java 48 | 49 | pom { 50 | name = 'JLineup CLI' 51 | description = 'The cli version of JLineup' 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/configuration/JacksonConfiguration.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web.configuration; 2 | 3 | import com.fasterxml.jackson.core.json.JsonReadFeature; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 6 | import com.fasterxml.jackson.databind.SerializationFeature; 7 | import com.fasterxml.jackson.databind.json.JsonMapper; 8 | import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; 9 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS; 14 | import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS; 15 | import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT; 16 | 17 | @Configuration 18 | public class JacksonConfiguration { 19 | 20 | @Bean 21 | public ObjectMapper objectMapper() { 22 | JsonMapper objectMapper = JsonMapper.builder() 23 | .configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true) 24 | .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS, true) 25 | .configure(ACCEPT_CASE_INSENSITIVE_ENUMS, true) 26 | .configure(ALLOW_COMMENTS, true) 27 | .configure(INDENT_OUTPUT, true) 28 | .build(); 29 | objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE); 30 | objectMapper.registerModule(new JavaTimeModule()); 31 | objectMapper.registerModule(new Jdk8Module()); 32 | objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 33 | return objectMapper; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/ReportV2.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import de.otto.jlineup.browser.BrowserStep; 4 | import de.otto.jlineup.config.JobConfig; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | 10 | public class ReportV2 { 11 | 12 | public final Summary summary; 13 | public final JobConfig config; 14 | public final List urlReports; 15 | public final Map browsers; 16 | 17 | public ReportV2(Summary summary, JobConfig jobConfig, List urlReports, Map browsers) { 18 | this.summary = summary; 19 | this.config = jobConfig.sanitize(); 20 | this.urlReports = urlReports; 21 | this.browsers = browsers; 22 | } 23 | 24 | @UsedInTemplate 25 | public String getBrowser(String step) { 26 | return browsers.get(BrowserStep.valueOf(step)); 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return "ReportV2{" + 32 | "summary=" + summary + 33 | ", config=" + config + 34 | ", urlReports=" + urlReports + 35 | ", browsers=" + browsers + 36 | '}'; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | ReportV2 reportV2 = (ReportV2) o; 44 | return Objects.equals(summary, reportV2.summary) && Objects.equals(config, reportV2.config) && Objects.equals(urlReports, reportV2.urlReports) && Objects.equals(browsers, reportV2.browsers); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(summary, config, urlReports, browsers); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/test/resources/lineup_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "urls": { 3 | "https://www.otto.de": { 4 | "paths": [ 5 | "/", 6 | "multimedia" 7 | ], 8 | "setup-paths": [ 9 | "setup" 10 | ], 11 | "cleanup-paths": [ 12 | "cleanup" 13 | ], 14 | "max-diff": 0.05, 15 | "cookies": [ 16 | { 17 | "name": "testcookie1", 18 | "value": "true" 19 | }, 20 | { 21 | "name": "testcookie2", 22 | "value": "1" 23 | } 24 | ], 25 | "local-storage": { 26 | "teststorage": "{'testkey':{'value':true,'timestamp':9467812242358}}" 27 | }, 28 | "session-storage": { 29 | "testsession": "{'testkey':{'value':true,'timestamp':9467812242358}}" 30 | }, 31 | "env-mapping": { 32 | "live": "www" 33 | }, 34 | "resolutions": [ 35 | 600, 36 | 800, 37 | 1200 38 | ], 39 | "wait-after-page-load": 2, 40 | "wait-after-scroll": 1, 41 | "max-scroll-height": 50000, 42 | "warmup-browser-cache-time": 3, 43 | "javascript": "console.log('Moin!');" 44 | }, 45 | "http://www.google.de": { 46 | "paths": [ 47 | "/" 48 | ], 49 | "max-diff": 0.05, 50 | "resolutions": [ 51 | 1200 52 | ], 53 | "cookies": [ 54 | { 55 | "name": "classic", 56 | "value": "true" 57 | } 58 | ], 59 | "alternating-cookies": [ 60 | [ 61 | { 62 | "name": "alternating", 63 | "value": "case1" 64 | } 65 | ], 66 | [ 67 | { 68 | "name": "alternating", 69 | "value": "case2" 70 | } 71 | ] 72 | ] 73 | } 74 | }, 75 | "browser": "firefox-headless", 76 | "wait-after-page-load": 1, 77 | "threads": 12, 78 | "page-load-timeout": 60, 79 | "screenshot-retries": 2 80 | } -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/browser/LogErrorCheckerTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import de.otto.jlineup.config.JobConfig; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.MockitoJUnitRunner; 8 | import org.openqa.selenium.WebDriver; 9 | import org.openqa.selenium.WebDriverException; 10 | import org.openqa.selenium.logging.LogEntries; 11 | import org.openqa.selenium.logging.LogEntry; 12 | import org.openqa.selenium.logging.LogType; 13 | 14 | import java.time.Instant; 15 | import java.util.logging.Level; 16 | 17 | import static com.google.common.collect.ImmutableList.of; 18 | import static org.junit.Assert.fail; 19 | import static org.mockito.Answers.RETURNS_DEEP_STUBS; 20 | import static org.mockito.Mockito.when; 21 | 22 | @RunWith(MockitoJUnitRunner.class) 23 | public class LogErrorCheckerTest { 24 | 25 | @Mock(answer = RETURNS_DEEP_STUBS) 26 | private WebDriver webDriver; 27 | 28 | @Test(expected = WebDriverException.class) 29 | public void shouldCheckForErrors() { 30 | //given 31 | when(webDriver.manage().logs().get(LogType.BROWSER)).thenReturn(new LogEntries(of(new LogEntry(Level.SEVERE, Instant.now().toEpochMilli(), "Fehler!")))); 32 | 33 | //when 34 | new LogErrorChecker().checkForErrors(webDriver, JobConfig.exampleConfig()); 35 | 36 | //then expect exception 37 | fail(); 38 | } 39 | 40 | @Test 41 | public void shouldNotCheckForErrorsIfDisabled() { 42 | //given 43 | when(webDriver.manage().logs().get(LogType.BROWSER)).thenReturn(new LogEntries(of(new LogEntry(Level.SEVERE, Instant.now().toEpochMilli(), "Fehler!")))); 44 | JobConfig jobConfig = JobConfig.exampleConfigBuilder().withCheckForErrorsInLog(false).build(); 45 | 46 | //when 47 | new LogErrorChecker().checkForErrors(webDriver, jobConfig); 48 | 49 | //then expect no exception 50 | 51 | } 52 | } -------------------------------------------------------------------------------- /cli/src/test/java/de/otto/jlineup/cli/acceptance/MirrorOutputStream.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.cli.acceptance; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public final class MirrorOutputStream extends OutputStream { 7 | 8 | private final OutputStream one; 9 | private final OutputStream two; 10 | 11 | MirrorOutputStream(OutputStream out, OutputStream tee) { 12 | if (out == null || tee == null) { 13 | throw new NullPointerException(); 14 | } 15 | this.one = out; 16 | this.two = tee; 17 | } 18 | 19 | @Override 20 | public void write(int b) throws IOException { 21 | one.write(b); 22 | two.write(b); 23 | } 24 | 25 | @Override 26 | public void write(byte[] b) throws IOException { 27 | one.write(b); 28 | two.write(b); 29 | } 30 | 31 | @Override 32 | public void write(byte[] b, int off, int len) throws IOException { 33 | one.write(b, off, len); 34 | two.write(b, off, len); 35 | } 36 | 37 | @Override 38 | public void flush() throws IOException { 39 | one.flush(); 40 | two.flush(); 41 | } 42 | 43 | @Override 44 | public void close() throws IOException { 45 | Exception oneEx = null; 46 | Exception twoEx = null; 47 | try { 48 | one.close(); 49 | } catch (IOException o) { 50 | oneEx = o; 51 | } 52 | try { 53 | two.close(); 54 | } catch (IOException t) { 55 | twoEx = t; 56 | } 57 | if (oneEx != null) { 58 | try { 59 | throw oneEx; 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } else if (twoEx != null) { 64 | try { 65 | throw twoEx; 66 | } catch (Exception e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /web/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'org.springframework.boot' 2 | 3 | dependencies { 4 | implementation project(":jlineup-core") 5 | implementation("org.springframework.boot:spring-boot-starter-web") 6 | implementation("org.yaml:snakeyaml") 7 | implementation("org.springframework.boot:spring-boot-starter-thymeleaf") 8 | implementation("de.otto.edison:edison-core:3.5.6") 9 | implementation("io.micrometer:micrometer-registry-prometheus") 10 | 11 | implementation("org.hibernate.validator:hibernate-validator") 12 | //Update commons-codec 13 | implementation("commons-codec:commons-codec") 14 | //Update embedded tomcat 15 | implementation("org.apache.tomcat.embed:tomcat-embed-core") 16 | implementation("org.apache.tomcat.embed:tomcat-embed-el") 17 | implementation("org.apache.tomcat.embed:tomcat-embed-websocket") 18 | 19 | testImplementation "org.junit.jupiter:junit-jupiter" 20 | testCompileOnly 'junit:junit' 21 | testRuntimeOnly "org.junit.vintage:junit-vintage-engine" 22 | testRuntimeOnly "org.junit.platform:junit-platform-launcher" 23 | testImplementation("org.springframework.boot:spring-boot-starter-test") 24 | testImplementation('io.rest-assured:rest-assured') 25 | testImplementation("org.awaitility:awaitility") 26 | 27 | annotationProcessor "org.springframework.boot:spring-boot-configuration-processor" 28 | } 29 | 30 | configurations.all { 31 | exclude group: 'commons-logging', module: 'commons-logging' 32 | } 33 | 34 | application { 35 | mainClass.set("de.otto.jlineup.web.JLineupWebApplication") 36 | } 37 | 38 | bootJar { 39 | mainClass = "de.otto.jlineup.web.JLineupWebApplication" 40 | } 41 | 42 | publishing { 43 | publications { 44 | mavenJava(MavenPublication) { 45 | groupId = "de.otto" 46 | artifactId = 'jlineup-web' 47 | from components.java 48 | pom { 49 | name = 'JLineup Web' 50 | description = 'The web variant of JLineup' 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /cli/src/main/java/de/otto/jlineup/cli/Utils.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.cli; 2 | 3 | import de.otto.jlineup.RunStepConfig; 4 | import de.otto.jlineup.config.JobConfig; 5 | 6 | import java.io.IOException; 7 | 8 | import static com.google.common.base.MoreObjects.firstNonNull; 9 | import static de.otto.jlineup.browser.BrowserUtils.RANDOM_FOLDER_PLACEHOLDER; 10 | import static java.util.Collections.emptyList; 11 | import static java.util.Collections.emptyMap; 12 | 13 | public class Utils { 14 | 15 | public static JobConfig readConfig(final JLineup parameters) throws IOException { 16 | return JobConfig.readConfig(parameters.getWorkingDirectory(), parameters.getConfigFile()); 17 | } 18 | 19 | public static JobConfig readMergeConfig(final JLineup parameters) throws IOException { 20 | return JobConfig.readConfig(parameters.getWorkingDirectory(), parameters.getMergeConfigFile()); 21 | } 22 | 23 | public static RunStepConfig convertCommandLineParametersToRunConfiguration(JLineup commandLineParameters) { 24 | return RunStepConfig.runStepConfigBuilder() 25 | .withWorkingDirectory(commandLineParameters.getWorkingDirectory()) 26 | .withScreenshotsDirectory(commandLineParameters.getScreenshotDirectory()) 27 | .withReportDirectory(commandLineParameters.getReportDirectory()) 28 | .withStep(commandLineParameters.getStep()) 29 | .withUrlReplacements(firstNonNull(commandLineParameters.getUrlReplacements(), emptyMap())) 30 | .withChromeParameters(firstNonNull(commandLineParameters.getChromeParameters() != null ? commandLineParameters.getChromeParameters().stream().map(param -> param.startsWith("--user-data-dir") ? param + "/" + RANDOM_FOLDER_PLACEHOLDER : param).toList() : null, emptyList())) 31 | .withFirefoxParameters(firstNonNull(commandLineParameters.getFirefoxParameters(), emptyList())) 32 | .withKeepExistingFiles(commandLineParameters.isKeepExisting()) 33 | .withRefreshUrl(commandLineParameters.getRefreshUrl()) 34 | .withCleanupProfile(commandLineParameters.isCleanupProfile()) 35 | .build(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /web/src/main/resources/templates/reports.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 |
11 |
12 |
13 |

Reports Overview

14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
IDUrlsStatusStartDuration
42
https://www.verylongurl.com/some/cool/path
https://www.anotherurl.com/bla/blub/bluuub/loooooooooooooooooong/loooooooooooooooooonger
OK2018-09-09T20:00:00.000Z10:20:30ReportLog
No runs since start.
41 |
42 |
43 |
44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /cli/graalvm/prepare-native-image-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## Uncomment to downloaded latest nightly 4 | #set -x 5 | #wget -O ../../graalvm/graalvm-dev.tar.gz https://github.com/graalvm/graalvm-ce-dev-builds/releases/latest/download/graalvm-ce-java11-linux-amd64-dev.tar.gz 6 | #mkdir ../../graalvm/graal-dev && tar -xzf ../../graalvm/graalvm-dev.tar.gz -C ../../graalvm/graal-dev --strip-components 1 7 | #set +x 8 | 9 | if [ -z ${GRAAL_HOME+x} ]; then 10 | 11 | if [[ $JAVA_HOME == *"grl"* ]]; then 12 | GRAAL_HOME=$JAVA_HOME 13 | else 14 | GRAAL_HOME="../../graalvm/graalvm-ce-java11-21.1.0/" 15 | #GRAAL_HOME="../../graalvm/graalvm-ce-java11-19.3.1/" 16 | fi 17 | fi 18 | 19 | "${GRAAL_HOME}"/bin/gu install native-image 20 | echo "GRAAL_HOME is set to '$GRAAL_HOME'" 21 | 22 | sudo apt-get install libfreetype6-dev 23 | 24 | #"${GRAAL_HOME}"/bin/native-image --expert-options-all 25 | 26 | BASEDIR=$(dirname "$0") 27 | cd "${BASEDIR}"/.. 28 | 29 | GRAAL_HOME=$(readlink -m ${GRAAL_HOME}) 30 | JAVA_HOME=${GRAAL_HOME} 31 | 32 | set -e 33 | 34 | echo "" 35 | echo "BUILDING JAR" 36 | echo "" 37 | 38 | cd .. 39 | #./gradlew compileJava shadowJar 40 | 41 | echo "" 42 | echo "DOING STUFF" 43 | echo "" 44 | 45 | echo "$JAVA_HOME" 46 | 47 | cd cli 48 | "${GRAAL_HOME}"/bin/java -agentlib:native-image-agent=config-output-dir=graalvm -jar build/libs/jlineup-cli-4.14.2-all.jar --config graalvm/lineup_chrome_headless.json --step before || true 49 | "${GRAAL_HOME}"/bin/java -agentlib:native-image-agent=config-merge-dir=graalvm -jar build/libs/jlineup-cli-4.14.2-all.jar --config graalvm/lineup_chrome_headless.json --step after || true 50 | "${GRAAL_HOME}"/bin/java -agentlib:native-image-agent=config-merge-dir=graalvm -jar build/libs/jlineup-cli-4.14.2-all.jar --config graalvm/lineup_firefox_headless.json --step before || true 51 | "${GRAAL_HOME}"/bin/java -agentlib:native-image-agent=config-merge-dir=graalvm -jar build/libs/jlineup-cli-4.14.2-all.jar --config graalvm/lineup_chrome.json --step before || true 52 | "${GRAAL_HOME}"/bin/java -agentlib:native-image-agent=config-merge-dir=graalvm -jar build/libs/jlineup-cli-4.14.2-all.jar --url www.otto.de --step before || true 53 | 54 | #-J-Djava.security.properties=graalvm/java.security.overrides \ 55 | -------------------------------------------------------------------------------- /web/src/main/java/de/otto/jlineup/web/configuration/JLineupWebMvcConfigurationSupport.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.web.configuration; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.http.converter.HttpMessageConverter; 9 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 10 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; 12 | 13 | import java.io.IOException; 14 | import java.nio.file.Files; 15 | import java.nio.file.Paths; 16 | import java.util.List; 17 | 18 | import static java.lang.invoke.MethodHandles.lookup; 19 | 20 | @Configuration 21 | public class JLineupWebMvcConfigurationSupport extends WebMvcConfigurationSupport { 22 | 23 | private final static Logger LOG = LoggerFactory.getLogger(lookup().lookupClass()); 24 | private final ObjectMapper objectMapper; 25 | private final JLineupWebProperties properties; 26 | 27 | @Autowired 28 | public JLineupWebMvcConfigurationSupport(ObjectMapper objectMapper, JLineupWebProperties properties) { 29 | super(); 30 | this.objectMapper = objectMapper; 31 | this.properties = properties; 32 | } 33 | 34 | @Override 35 | public void configureMessageConverters(final List> converters) { 36 | converters.add(new MappingJackson2HttpMessageConverter(objectMapper)); 37 | addDefaultHttpMessageConverters(converters); 38 | super.configureMessageConverters(converters); 39 | } 40 | 41 | @Override 42 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 43 | 44 | try { 45 | Files.createDirectories(Paths.get(properties.getWorkingDirectory())); 46 | } catch (IOException e) { 47 | LOG.error("Cannot create JLineup working directory.", e); 48 | } 49 | 50 | registry.addResourceHandler("/reports/**") 51 | .addResourceLocations("file:" + properties.getWorkingDirectory()) 52 | .setCachePeriod(0); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/browser/LogErrorChecker.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import de.otto.jlineup.config.JobConfig; 4 | import org.openqa.selenium.*; 5 | import org.openqa.selenium.json.JsonException; 6 | import org.openqa.selenium.logging.LogEntries; 7 | import org.openqa.selenium.logging.LogType; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.time.Duration; 12 | import java.time.temporal.ChronoUnit; 13 | import java.util.concurrent.TimeUnit; 14 | import java.util.logging.Level; 15 | 16 | import static java.lang.invoke.MethodHandles.lookup; 17 | 18 | public class LogErrorChecker { 19 | 20 | private final static Logger LOG = LoggerFactory.getLogger(lookup().lookupClass()); 21 | 22 | void checkForErrors(WebDriver driver, JobConfig jobConfig) { 23 | if (jobConfig.checkForErrorsInLog) { 24 | LOG.debug("Checking for errors."); 25 | LogEntries logEntries; 26 | try { 27 | logEntries = driver.manage().logs().get(LogType.BROWSER); 28 | } catch (UnsupportedCommandException | JsonException e) { 29 | logEntries = null; 30 | } 31 | 32 | if (logEntries != null && !logEntries.getAll().isEmpty() && logEntries.getAll().get(0).getLevel().equals(Level.SEVERE)) { 33 | throw new WebDriverException(logEntries.getAll().get(0).getMessage()); 34 | } 35 | 36 | if (jobConfig.browser.isChrome()) { 37 | driver.manage().timeouts().implicitlyWait(Duration.ZERO); 38 | try { 39 | WebElement element = driver.findElement(By.className("error-code")); 40 | if (element != null && element.getText() != null) { 41 | element.getText(); 42 | throw new WebDriverException(element.getText()); 43 | } 44 | } catch (NoSuchElementException e) { 45 | //ignore 46 | } finally { 47 | driver.manage().timeouts().implicitlyWait(Duration.of(Browser.DEFAULT_IMPLICIT_WAIT_TIME_IN_SECONDS, ChronoUnit.SECONDS)); 48 | } 49 | } 50 | } else { 51 | LOG.debug("Not checking for errors in browser log."); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | SCRIPT_DIR=$(dirname $0) 5 | 6 | USER_JRELEASER_PROPERTIES=~/.jreleaser/config.toml 7 | 8 | check_configured() { 9 | grep -q $1 ${USER_JRELEASER_PROPERTIES} || ( echo "$1 not configured in ${USER_JRELEASER_PROPERTIES}. $2" && exit 1 ) 10 | } 11 | 12 | check_configuration() { 13 | if [ ! -f ${USER_JRELEASER_PROPERTIES} ]; then 14 | echo "${USER_JRELEASER_PROPERTIES} does not exist. We use JReleaser to release packages. Pleaser create the file and add the following variables:" 15 | echo "JRELEASER_GPG_PASSPHRASE=\"your_gpg_passphrase\"" 16 | echo "JRELEASER_GPG_PUBLIC_KEY=\"your_gpg_public_key\"" 17 | echo "JRELEASER_GPG_SECRET_KEY=\"your_gpg_secret_key\"" 18 | echo "JRELEASER_MAVENCENTRAL_USERNAME=\"your_maven_central_username\"" 19 | echo "JRELEASER_MAVENCENTRAL_PASSWORD=\"your_maven_central_password\"" 20 | echo "JRELEASER_GITHUB_TOKEN=\"your_github_token\"" 21 | exit 1 22 | fi 23 | 24 | check_configured "JRELEASER_GPG_PASSPHRASE" "This is the GPG passphrase for your GPG key" 25 | check_configured "JRELEASER_GPG_PUBLIC_KEY" "This is the GPG public key to sign the release. Use 'gpg --export ***KEYID*** | base64' to export the public key" 26 | check_configured "JRELEASER_GPG_SECRET_KEY" "This is the GPG secret key to sign the release. Use 'gpg --export-secret-keys ***KEYID*** | base64' to export the private key" 27 | check_configured "JRELEASER_MAVENCENTRAL_USERNAME" "This is the Maven Central username to release packages" 28 | check_configured "JRELEASER_MAVENCENTRAL_PASSWORD" "This is the Maven Central password to release packages" 29 | check_configured "JRELEASER_GITHUB_TOKEN" "This it the Github Token to release packages and release information to GitHub" 30 | # for GPG version >= 2.1: gpg --export-secret-keys >~/.gnupg/secring.gpg 31 | # gpg --send-keys --keyserver keys.openpgp.org yourKeyId 32 | } 33 | 34 | check_configuration 35 | 36 | set +e 37 | grep '_version = ".*-SNAPSHOT"' "$SCRIPT_DIR/build.gradle" 38 | SNAPSHOT=$? 39 | set -e 40 | 41 | if [[ $SNAPSHOT == 1 ]]; then 42 | echo "INFO: This is not a SNAPSHOT, I'll release to Maven Central during upload." 43 | else 44 | echo "INFO: This is a SNAPSHOT release. Packages will be released to GitHub packages only." 45 | fi 46 | 47 | "${SCRIPT_DIR}"/gradlew clean jreleaserConfig check 48 | #"${SCRIPT_DIR}"/gradlew jreleaserConfig check 49 | "${SCRIPT_DIR}"/gradlew build installBootDist publish 50 | "${SCRIPT_DIR}"/gradlew jreleaserFullRelease 51 | 52 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/image/AntiAliasingIgnoringComparatorTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.image; 2 | 3 | import org.junit.Test; 4 | 5 | import javax.imageio.ImageIO; 6 | import java.awt.image.BufferedImage; 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | import static de.otto.jlineup.config.JobConfig.DEFAULT_MAX_ANTI_ALIAS_COLOR_DISTANCE; 11 | import static org.hamcrest.CoreMatchers.is; 12 | import static org.junit.Assert.assertThat; 13 | 14 | public class AntiAliasingIgnoringComparatorTest { 15 | 16 | @Test 17 | public void shouldFindAntiAliasedPixel() throws IOException { 18 | //given 19 | final BufferedImage beforeImageBuffer = ImageIO.read(new File("src/test/resources/screenshots/cases/chrome_rounded_edges_before.png")); 20 | final BufferedImage afterImageBuffer = ImageIO.read(new File("src/test/resources/screenshots/cases/chrome_rounded_edges_after.png")); 21 | //final BufferedImage referenceImageBuffer = ImageIO.read(new File("src/test/resources/screenshots/cases/chrome_rounded_edges_DIFFERENCE_reference.png")); 22 | 23 | assertThat(AntiAliasingIgnoringComparator.checkIsAntialiased(beforeImageBuffer, afterImageBuffer, 180, 33, DEFAULT_MAX_ANTI_ALIAS_COLOR_DISTANCE), is(false)); 24 | assertThat(AntiAliasingIgnoringComparator.checkIsAntialiased(beforeImageBuffer, afterImageBuffer, 181, 33, DEFAULT_MAX_ANTI_ALIAS_COLOR_DISTANCE), is(true)); 25 | assertThat(AntiAliasingIgnoringComparator.checkIsAntialiased(beforeImageBuffer, afterImageBuffer, 182, 33, DEFAULT_MAX_ANTI_ALIAS_COLOR_DISTANCE), is(false)); 26 | } 27 | 28 | @Test 29 | public void shouldFindAntiAliasedPixeWithZeroTolerance() throws IOException { 30 | //given 31 | final BufferedImage beforeImageBuffer = ImageIO.read(new File("src/test/resources/screenshots/cases/chrome_rounded_edges_before.png")); 32 | final BufferedImage afterImageBuffer = ImageIO.read(new File("src/test/resources/screenshots/cases/chrome_rounded_edges_after.png")); 33 | //final BufferedImage referenceImageBuffer = ImageIO.read(new File("src/test/resources/screenshots/cases/chrome_rounded_edges_DIFFERENCE_reference.png")); 34 | 35 | assertThat(AntiAliasingIgnoringComparator.checkIsAntialiased(beforeImageBuffer, afterImageBuffer, 180, 33, 0), is(false)); 36 | assertThat(AntiAliasingIgnoringComparator.checkIsAntialiased(beforeImageBuffer, afterImageBuffer, 181, 33, 0), is(true)); 37 | assertThat(AntiAliasingIgnoringComparator.checkIsAntialiased(beforeImageBuffer, afterImageBuffer, 182, 33, 0), is(false)); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lambda/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | awsSdkVersion = '2.39.1' 4 | amazonAwsJavaSdkVersion = '1.12.793' 5 | } 6 | } 7 | 8 | plugins { 9 | id 'java-library' 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | 14 | dependencies { 15 | implementation project(":jlineup-core") 16 | implementation "org.slf4j:slf4j-api" 17 | implementation("software.amazon.awssdk:lambda:${awsSdkVersion}") 18 | implementation("software.amazon.awssdk:apache-client:${awsSdkVersion}") 19 | implementation("software.amazon.awssdk:s3:${awsSdkVersion}") 20 | implementation("software.amazon.awssdk:s3-transfer-manager:${awsSdkVersion}") 21 | implementation("software.amazon.awssdk:sso:${awsSdkVersion}") 22 | implementation("software.amazon.awssdk:ssooidc:${awsSdkVersion}") 23 | implementation("software.amazon.awssdk:sts:${awsSdkVersion}") 24 | implementation("software.amazon.awssdk.crt:aws-crt:0.40.0") 25 | implementation("com.amazonaws:aws-lambda-java-core:1.4.0") 26 | implementation("com.amazonaws:aws-lambda-java-events:3.16.1") 27 | implementation("com.amazonaws:aws-java-sdk-core:${amazonAwsJavaSdkVersion}") 28 | testImplementation platform("com.amazonaws:aws-xray-recorder-sdk-bom:2.20.0") 29 | testImplementation("com.amazonaws:aws-xray-recorder-sdk-core") 30 | testImplementation("com.amazonaws:aws-xray-recorder-sdk-aws-sdk") 31 | testImplementation("com.amazonaws:aws-xray-recorder-sdk-aws-sdk-instrumentor") 32 | testImplementation("com.amazonaws:aws-java-sdk-xray:${amazonAwsJavaSdkVersion}") 33 | runtimeOnly "com.amazonaws:aws-lambda-java-log4j2:1.6.0" 34 | 35 | testImplementation "org.junit.jupiter:junit-jupiter-api" 36 | testRuntimeOnly "org.junit.platform:junit-platform-launcher" 37 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine" 38 | } 39 | 40 | application { 41 | mainClass.set("de.otto.jlineup.lambda.JLineupHandler") 42 | } 43 | 44 | bootJar { 45 | mainClass = "de.otto.jlineup.lambda.JLineupHandler" 46 | } 47 | 48 | tasks.register('copyDependenciesToLib', Copy) { 49 | into project.layout.buildDirectory.dir("output/lib") 50 | from configurations.runtimeClasspath 51 | } 52 | 53 | build.dependsOn copyDependenciesToLib 54 | 55 | application { 56 | mainClass.set("de.otto.jlineup.lambda.Main") 57 | } 58 | 59 | publishing { 60 | publications { 61 | mavenJava(MavenPublication) { 62 | groupId = "de.otto" 63 | artifactId = 'jlineup-lambda' 64 | from components.java 65 | pom { 66 | name = 'JLineup Lambda' 67 | description = 'The AWS lambda handler of JLineup' 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /core/src/main/resources/templates/report_not_finished.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JLineup in progress... 5 | 6 | 7 | 8 | 9 | 10 | 11 | 76 | 77 | 78 | 79 | 80 |
81 |

JLineup unfinished report

82 |

83 |

The JLineup run was not finished yet (before and after steps have to be finished to generate a screenshot comparison report) or ran into an error. You might check the JLineup run log file for details.

84 |
85 | 86 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /cli/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.out 5 | 6 | INFO 7 | ACCEPT 8 | DENY 9 | 10 | 11 | %msg%n 12 | 13 | 14 | 15 | 16 | System.out 17 | 18 | WARN 19 | ACCEPT 20 | DENY 21 | 22 | 23 | %msg%n 24 | 25 | 26 | 27 | 28 | System.out 29 | 30 | DEBUG 31 | ACCEPT 32 | DENY 33 | 34 | 35 | %msg%n 36 | 37 | 38 | 39 | 40 | System.err 41 | 42 | ERROR 43 | ACCEPT 44 | DENY 45 | 46 | 47 | %msg%n 48 | 49 | 50 | 51 | 52 | 53 | reportlogname 54 | unknown 55 | 56 | 57 | 58 | ${reportlogname} 59 | 60 | %d{HH:mm:ss:SSS} | %-5level | %thread | %logger{20} | %msg%n%rEx 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /cli/graalvm/build-native-image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ## Uncomment to downloaded latest nightly 4 | #set -x 5 | #wget -O ../../graalvm/graalvm-dev.tar.gz https://github.com/graalvm/graalvm-ce-dev-builds/releases/latest/download/graalvm-ce-java11-linux-amd64-dev.tar.gz 6 | #mkdir ../../graalvm/graal-dev && tar -xzf ../../graalvm/graalvm-dev.tar.gz -C ../../graalvm/graal-dev --strip-components 1 7 | #set +x 8 | 9 | if [ -z ${GRAAL_HOME+x} ]; then 10 | 11 | if [[ $JAVA_HOME == *"grl"* ]]; then 12 | GRAAL_HOME=$JAVA_HOME 13 | else 14 | GRAAL_HOME="../../graalvm/graalvm-ce-java11-21.1.0/" 15 | #GRAAL_HOME="../../graalvm/graalvm-ce-java11-19.3.1/" 16 | fi 17 | fi 18 | 19 | "${GRAAL_HOME}"/bin/gu install native-image 20 | echo "GRAAL_HOME is set to '$GRAAL_HOME'" 21 | 22 | #"${GRAAL_HOME}"/bin/native-image --expert-options-all 23 | 24 | BASEDIR=$(dirname "$0") 25 | cd "${BASEDIR}"/.. 26 | 27 | GRAAL_HOME=$(readlink -m ${GRAAL_HOME}) 28 | JAVA_HOME=${GRAAL_HOME} 29 | 30 | set -e 31 | 32 | echo "" 33 | echo "BUILDING JAR" 34 | echo "" 35 | 36 | cd .. 37 | ./gradlew compileJava shadowJar 38 | 39 | echo "" 40 | echo "START BUILDING NATIVE IMAGE" 41 | echo "" 42 | 43 | cd cli 44 | #../../graalvm/graalvm/bin/java -agentlib:native-image-agent -jar jlineup-cli-4.1.1-SNAPSHOT-all.jar --url https://www.otto.de --step after 45 | #-J-Djava.security.properties=graalvm/java.security.overrides \ 46 | 47 | "${GRAAL_HOME}"/bin/native-image \ 48 | --no-server \ 49 | -H:IncludeResources='.*properties$|.*html$|.*xml$' \ 50 | -H:+ReportExceptionStackTraces \ 51 | -H:+JNI \ 52 | --enable-https \ 53 | --enable-http \ 54 | --enable-url-protocols=http,https \ 55 | --enable-all-security-services \ 56 | --no-fallback \ 57 | --allow-incomplete-classpath \ 58 | -H:+AddAllCharsets \ 59 | `#-H:ReflectionConfigurationFiles=graalvm/reflect.json` \ 60 | -H:ConfigurationFileDirectories=graalvm/ \ 61 | `#--initialize-at-build-time=com.fasterxml.jackson,javassist.ClassPool` \ 62 | --verbose \ 63 | --report-unsupported-elements-at-runtime \ 64 | `#--static` \ 65 | `#-H:+TraceSecurityServices` \ 66 | `#-H:+TraceClassInitialization` \ 67 | -jar build/libs/jlineup-cli-4.14.2-all.jar 68 | 69 | echo "" 70 | echo "DONE BUILDING NATIVE IMAGE" 71 | echo "" 72 | 73 | #exit 74 | 75 | echo "" 76 | echo "STARTING TEST RUN" 77 | echo "" 78 | 79 | mv jlineup-cli-4.14.2-all build/libs/jlineup-cli-4.14.2-all 80 | rm ~/.m2/repository/webdriver -rf 81 | ./build/libs/jlineup-cli-4.14.2-all -Dwdm.architecture=X64 --config graalvm/lineup_chrome_headless.json --step before 82 | 83 | set +e 84 | 85 | ./build/libs/jlineup-cli-4.14.2-all -Dwdm.architecture=X64 --config graalvm/lineup_chrome_headless.json --step after 86 | 87 | set -e 88 | 89 | mv ./build/libs/jlineup-cli-*-all ./build/libs/jlineup 90 | cd ./build/libs 91 | 92 | tar -czf jlineup-cli-linux-amd64.tar.gz jlineup 93 | 94 | echo "" 95 | echo "FINISHED" 96 | echo "" 97 | -------------------------------------------------------------------------------- /.github/workflows/delete-old-packages.yml: -------------------------------------------------------------------------------- 1 | name: "Delete old SNAPSHOTS" 2 | 3 | on: 4 | schedule: 5 | - cron: '37 13 * * *' 6 | workflow_dispatch: 7 | inputs: 8 | git-ref: 9 | description: Git Ref (Optional) 10 | required: false 11 | 12 | permissions: # added using https://github.com/step-security/secure-repo 13 | contents: read 14 | 15 | jobs: 16 | delete: 17 | permissions: 18 | packages: write # for actions/delete-package-versions to delete packages 19 | name: "Delete old SNAPSHOTS" 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Harden the runner (Audit all outbound calls) 23 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 24 | with: 25 | egress-policy: audit 26 | 27 | - name: "Delete de.otto.jlineup-core" 28 | uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 29 | with: 30 | package-name: 'de.otto.jlineup-core' 31 | package-type: 'maven' 32 | min-versions-to-keep: 10 33 | delete-only-pre-release-versions: "true" 34 | - name: "Delete de.otto.jlineup-cli" 35 | uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 36 | with: 37 | package-name: 'de.otto.jlineup-cli' 38 | package-type: 'maven' 39 | min-versions-to-keep: 10 40 | delete-only-pre-release-versions: "true" 41 | - name: "Delete de.otto.jlineup-cli-lambda" 42 | uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 43 | with: 44 | package-name: 'de.otto.jlineup-cli-lambda' 45 | package-type: 'maven' 46 | min-versions-to-keep: 10 47 | delete-only-pre-release-versions: "true" 48 | - name: "Delete de.otto.jlineup-web" 49 | uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 50 | with: 51 | package-name: 'de.otto.jlineup-web' 52 | package-type: 'maven' 53 | min-versions-to-keep: 10 54 | delete-only-pre-release-versions: "true" 55 | - name: "Delete de.otto.jlineup-web-lambda" 56 | uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 57 | with: 58 | package-name: 'de.otto.jlineup-web-lambda' 59 | package-type: 'maven' 60 | min-versions-to-keep: 10 61 | delete-only-pre-release-versions: "true" 62 | - name: "Delete de.otto.jlineup-lambda" 63 | uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0 64 | with: 65 | package-name: 'de.otto.jlineup-lambda' 66 | package-type: 'maven' 67 | min-versions-to-keep: 10 68 | delete-only-pre-release-versions: "true" 69 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/GlobalOptions.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Properties; 6 | 7 | import static de.otto.jlineup.GlobalOption.*; 8 | 9 | public class GlobalOptions { 10 | 11 | private final static org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(GlobalOptions.class); 12 | 13 | private final static String DEFAULT_LAMBDA_FUNCTION_NAME = "jlineup-lambda"; 14 | private final static String DEFAULT_LAMBDA_AWS_PROFILE = "default"; 15 | private final static String DEFAULT_LAMBDA_S3_BUCKET = "jlineup-lambda"; 16 | 17 | private final static String DEFAULT_CROP_LAST_SCREENSHOT = "false"; 18 | 19 | static private final Map options; 20 | 21 | static { 22 | options = new HashMap<>(); 23 | 24 | Properties appProps = new Properties(); 25 | try { 26 | appProps.load(GlobalOptions.class.getResourceAsStream("/settings.properties")); 27 | } catch (Exception e) { 28 | LOG.debug("No settings found"); 29 | } 30 | 31 | loadOption(appProps, "JLINEUP_LAMBDA_FUNCTION_NAME", "jlineup.lambda.function-name", DEFAULT_LAMBDA_FUNCTION_NAME, JLINEUP_LAMBDA_FUNCTION_NAME); 32 | loadOption(appProps, "JLINEUP_AWS_PROFILE", "jlineup.lambda.aws-profile", DEFAULT_LAMBDA_AWS_PROFILE, JLINEUP_LAMBDA_AWS_PROFILE); 33 | loadOption(appProps, "JLINEUP_LAMBDA_S3_BUCKET", "jlineup.lambda.s3-bucket", DEFAULT_LAMBDA_S3_BUCKET, JLINEUP_LAMBDA_S3_BUCKET); 34 | loadOption(appProps, "JLINEUP_CROP_LAST_SCREENSHOT", "jlineup.crop-last-screenshot", DEFAULT_CROP_LAST_SCREENSHOT, JLINEUP_CROP_LAST_SCREENSHOT); 35 | 36 | loadOption(appProps, "JLINEUP_CHROME_VERSION", "jlineup.chrome-version", null, JLINEUP_CHROME_VERSION); 37 | loadOption(appProps, "JLINEUP_FIREFOX_VERSION", "jlineup.firefox-version", null, JLINEUP_FIREFOX_VERSION); 38 | } 39 | 40 | private static void loadOption(Properties appProps, String key, String property, String defaultValue, GlobalOption option) { 41 | if (System.getenv(key) != null) { 42 | options.put(option, System.getenv(key)); 43 | } else if (appProps.getProperty(property) != null) { 44 | options.put(option, appProps.getProperty(property)); 45 | } else { 46 | options.put(option, defaultValue); 47 | } 48 | } 49 | 50 | public static void setOption(GlobalOption option, String value) { 51 | options.put(option, value); 52 | } 53 | 54 | public static String getOption(GlobalOption option) { 55 | return options.get(option); 56 | } 57 | 58 | public static String asString() { 59 | return "GlobalOptions{" + 60 | "options=" + String.join(", ", options.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).toList()) + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/report/JSONReportWriterV1Test.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import de.otto.jlineup.config.DeviceConfig; 4 | import de.otto.jlineup.config.JobConfig; 5 | import de.otto.jlineup.file.FileService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | import org.mockito.Mockito; 10 | 11 | import static java.lang.System.lineSeparator; 12 | import static java.util.Collections.singletonList; 13 | import static java.util.Collections.singletonMap; 14 | import static org.mockito.MockitoAnnotations.initMocks; 15 | 16 | public class JSONReportWriterV1Test { 17 | 18 | private JSONReportWriter_V1 testee; 19 | 20 | @Mock 21 | private FileService fileServiceMock; 22 | 23 | @Before 24 | public void setup() { 25 | initMocks(this); 26 | testee = new JSONReportWriter_V1(fileServiceMock); 27 | } 28 | 29 | @Test 30 | public void shouldWriteComparisonReportAsJson() throws Exception { 31 | 32 | ScreenshotComparisonResult screenshotComparisonResult = 33 | new ScreenshotComparisonResult(1887, "url", DeviceConfig.deviceConfig(1337, 1887), 1979, 0d, 0d, "before", "after", "differenceImageFileName", 0); 34 | final Summary localSummary = new Summary(false, 0d, 0d, 0); 35 | final Summary globalSummary = new Summary(false, 0d, 0d, 0); 36 | Report report = new Report(globalSummary, singletonMap("test", new UrlReport(singletonList(screenshotComparisonResult), localSummary)), JobConfig.exampleConfig()); 37 | 38 | String expectedString = "[ {" + lineSeparator() + 39 | " \"contextHash\" : 1887," + lineSeparator() + 40 | " \"deviceConfig\" : {" + lineSeparator() + 41 | " \"width\" : 1337," + lineSeparator() + 42 | " \"height\" : 1887," + lineSeparator() + 43 | " \"pixelRatio\" : 1.0," + lineSeparator() + 44 | " \"deviceName\" : \"DESKTOP\"," + lineSeparator() + 45 | " \"touch\" : false" + lineSeparator() + 46 | " }," + lineSeparator() + 47 | " \"verticalScrollPosition\" : 1979," + lineSeparator() + 48 | " \"difference\" : 0.0," + lineSeparator() + 49 | " \"maxDetectedColorDifference\" : 0.0," + lineSeparator() + 50 | " \"screenshotBeforeFileName\" : \"before\"," + lineSeparator() + 51 | " \"screenshotAfterFileName\" : \"after\"," + lineSeparator() + 52 | " \"differenceImageFileName\" : \"differenceImageFileName\"," + lineSeparator() + 53 | " \"acceptedDifferentPixels\" : 0," + lineSeparator() + 54 | " \"url\" : \"url\"" + lineSeparator() + 55 | "} ]"; 56 | 57 | testee.writeComparisonReportAsJson(report); 58 | 59 | Mockito.verify(fileServiceMock).writeJsonReport(expectedString); 60 | } 61 | } -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/report/ReportGeneratorV2.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.report; 2 | 3 | import de.otto.jlineup.config.JobConfig; 4 | import de.otto.jlineup.file.FileService; 5 | 6 | import java.util.*; 7 | import java.util.stream.Collectors; 8 | 9 | public class ReportGeneratorV2 { 10 | 11 | private final FileService fileService; 12 | 13 | public ReportGeneratorV2(FileService fileService) { 14 | this.fileService = fileService; 15 | } 16 | 17 | public ReportV2 generateReport(Map> screenshotComparisonResultLists, JobConfig config) { 18 | List resultList = screenshotComparisonResultLists.values().stream().flatMap(List::stream).collect(Collectors.toList()); 19 | final Summary summary = getSummary(resultList); 20 | 21 | ArrayList urlReports = new ArrayList<>(); 22 | for (Map.Entry> resultForUrl : screenshotComparisonResultLists.entrySet()) { 23 | Summary urlSummary = getSummary(resultForUrl.getValue()); 24 | 25 | Map> resultsPerContextHash = resultForUrl.getValue().stream().collect(Collectors.groupingBy(res -> res.contextHash, Collectors.mapping(res -> res, Collectors.toList()))); 26 | ArrayList contextReports = new ArrayList<>(); 27 | for (Map.Entry> resultPerHash : resultsPerContextHash.entrySet()) { 28 | Summary contextSummary = getSummary(resultPerHash.getValue()); 29 | ContextReport contextReport = new ContextReport(resultPerHash.getKey(), fileService.getRecordedContext(resultPerHash.getKey()), contextSummary, resultPerHash.getValue()); 30 | contextReports.add(contextReport); 31 | } 32 | 33 | contextReports.sort(Comparator.comparing(ContextReport::getUrl).thenComparing(ContextReport::getWidth).thenComparing(ContextReport::getShownCookiesString)); 34 | 35 | UrlReportV2 urlReport = new UrlReportV2(resultForUrl.getKey(), config.urls.get(resultForUrl.getKey()).url, urlSummary, contextReports); 36 | urlReports.add(urlReport); 37 | } 38 | 39 | return new ReportV2(summary, config, urlReports, fileService.getBrowsers()); 40 | } 41 | 42 | private Summary getSummary(List resultList) { 43 | final double differenceSum = resultList.stream().mapToDouble(scr -> scr.difference).sum(); 44 | final OptionalDouble differenceMax = resultList.stream().mapToDouble(scr -> scr.difference).max(); 45 | final int acceptedDifferentPixelsSum = resultList.stream().mapToInt(scr -> scr.acceptedDifferentPixels).sum(); 46 | return new Summary(differenceSum > 0, differenceSum, differenceMax.orElseGet(() -> 0), acceptedDifferentPixelsSum); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/java/de/otto/jlineup/JacksonWrapper.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.core.json.JsonReadFeature; 5 | import com.fasterxml.jackson.databind.ObjectMapper; 6 | import com.fasterxml.jackson.databind.PropertyNamingStrategies; 7 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 8 | import com.fasterxml.jackson.databind.json.JsonMapper; 9 | import de.otto.jlineup.config.JobConfig; 10 | import de.otto.jlineup.file.FileTracker; 11 | 12 | import java.io.File; 13 | import java.io.IOException; 14 | import java.io.Reader; 15 | 16 | import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS; 17 | import static com.fasterxml.jackson.databind.MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS; 18 | import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT; 19 | 20 | public class JacksonWrapper { 21 | 22 | private static final JsonMapper objectMapper = JsonMapper.builder() 23 | .configure(JsonReadFeature.ALLOW_TRAILING_COMMA, true) 24 | .configure(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS, true) 25 | .configure(ACCEPT_CASE_INSENSITIVE_ENUMS, true) 26 | .configure(ALLOW_COMMENTS, true) 27 | .configure(INDENT_OUTPUT, true) 28 | .build(); 29 | 30 | static { 31 | objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.KEBAB_CASE); 32 | } 33 | 34 | private static ObjectMapper objectMapper() { 35 | return objectMapper; 36 | } 37 | 38 | public static String serializeObject(Object object) { 39 | try { 40 | return objectMapper().writeValueAsString(object); 41 | } catch (JsonProcessingException e) { 42 | throw new RuntimeException("There is a problem while writing the " + object.getClass().getCanonicalName() + " with Jackson.", e); 43 | } 44 | } 45 | 46 | public static String serializeObjectWithPropertyNamingStrategy(Object object, PropertyNamingStrategy propertyNamingStrategy) { 47 | try { 48 | return objectMapper().copy().setPropertyNamingStrategy(propertyNamingStrategy).writeValueAsString(object); 49 | } catch (JsonProcessingException e) { 50 | throw new RuntimeException("There is a problem while writing the " + object.getClass().getCanonicalName() + " with Jackson.", e); 51 | } 52 | } 53 | 54 | public static JobConfig deserializeConfig(Reader reader) { 55 | try { 56 | return objectMapper().readValue(reader, JobConfig.class); 57 | } catch (IOException e) { 58 | throw new RuntimeException("Error reading config into object.", e); 59 | } 60 | } 61 | 62 | public static FileTracker readFileTrackerFile(File file) { 63 | try { 64 | return objectMapper().readValue(file, FileTracker.class); 65 | } catch (IOException e) { 66 | throw new RuntimeException("Could not read FileTracker file.", e); 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /dependency-check-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | ^pkg:maven/org\.springframework/spring\-web@.*$ 8 | CVE-2016-1000027 9 | 10 | 11 | 14 | ^pkg:maven/org\.yaml/snakeyaml@.*$ 15 | CVE-2022-1471 16 | CVE-2021-4235 17 | CVE-2022-3064 18 | 19 | 20 | 23 | ^pkg:maven/software\.amazon\.awssdk\.crt/aws\-crt@.*$ 24 | CVE-2022-31159 25 | 26 | 27 | 30 | ^pkg:maven/.*commons.*$ 31 | CVE-2021-37533 32 | 33 | 34 | 37 | ^pkg:maven/.*$ 38 | CVE-2021-4277 39 | 40 | 41 | 44 | ^pkg:maven/.*$ 45 | CVE-2020-8908 46 | 47 | 48 | 51 | ^pkg:maven/.*$ 52 | CVE-2023-35116 53 | 54 | 55 | 58 | ^pkg:maven/.*$ 59 | CVE-2023-33546 60 | 61 | 62 | 65 | ^pkg:maven/.*$ 66 | CVE-2023-22006 67 | 68 | 69 | 70 | 73 | ^pkg:maven/.*$ 74 | CVE-2023-38286 75 | 76 | 77 | 80 | ^pkg:maven/io\.netty/netty-.*@.*$ 81 | CVE-2023-4586 82 | 83 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/browser/FakeWebServerController.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.browser; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.stereotype.Controller; 10 | import org.springframework.web.bind.annotation.CookieValue; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | 13 | import java.util.Map; 14 | 15 | import static java.lang.invoke.MethodHandles.lookup; 16 | 17 | @Controller 18 | @SpringBootApplication 19 | public class FakeWebServerController { 20 | 21 | private final static Logger LOG = LoggerFactory.getLogger(lookup().lookupClass()); 22 | 23 | @GetMapping({"/200", "/200/ "}) 24 | public ResponseEntity get200() { 25 | return new ResponseEntity<>("This is 200!", HttpStatus.OK); 26 | } 27 | 28 | @GetMapping({"/403", "/403/"}) 29 | public ResponseEntity get403() { 30 | return new ResponseEntity<>("This is 403!", HttpStatus.FORBIDDEN); 31 | } 32 | 33 | @GetMapping({"/404", "/404/"}) 34 | public ResponseEntity get404() { 35 | return new ResponseEntity<>("This is 404!", HttpStatus.NOT_FOUND); 36 | } 37 | 38 | @GetMapping({"/500", "/500/"}) 39 | public ResponseEntity get500() { 40 | return new ResponseEntity<>("This is 500!", HttpStatus.INTERNAL_SERVER_ERROR); 41 | } 42 | 43 | @GetMapping({"/params", "/params/"}) 44 | public ResponseEntity getParamsPageWithSlash(HttpServletRequest request) { 45 | Map parameterMap = request.getParameterMap(); 46 | if (parameterMap.get("param2")[0].endsWith("/")) { 47 | return new ResponseEntity<>("Bad Request", HttpStatus.BAD_REQUEST); 48 | } 49 | return new ResponseEntity<>("Params / page!", HttpStatus.OK); 50 | } 51 | 52 | @GetMapping({"/somerootpath", "/somerootpath/"}) 53 | public ResponseEntity getNotFoundOnRootPath() { 54 | return new ResponseEntity<>("This is Not Found!", HttpStatus.NOT_FOUND); 55 | } 56 | 57 | @GetMapping({"/somerootpath/somevalidsubpath", "/somerootpath/somevalidsubpath/"}) 58 | public ResponseEntity getPageOnDeeperPath() { 59 | return new ResponseEntity<>("Hallo! This is valid!", HttpStatus.OK); 60 | } 61 | 62 | @GetMapping({"/cookies","/cookies/"}) 63 | public ResponseEntity testAlteringCookies(@CookieValue(value = "alternating", required = false) String alternatingCookieValue, HttpServletRequest request) { 64 | LOG.info("Request URI is {}", request.getRequestURI()); 65 | LOG.info("Cookie value is {}", alternatingCookieValue); 66 | if (alternatingCookieValue == null) { 67 | return new ResponseEntity<>("No cookie found", HttpStatus.OK); 68 | } 69 | return new ResponseEntity<>("Alternating cookie value is " + alternatingCookieValue, HttpStatus.valueOf(alternatingCookieValue)); 70 | } 71 | 72 | /** 73 | * Returns an empty favicon to avoid 404 errors 74 | */ 75 | @GetMapping("favicon.ico") 76 | public ResponseEntity favicon() { 77 | return new ResponseEntity<>("", HttpStatus.OK); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /core/src/test/java/de/otto/jlineup/config/JobConfigValidatorTest.java: -------------------------------------------------------------------------------- 1 | package de.otto.jlineup.config; 2 | 3 | import de.otto.jlineup.browser.Browser; 4 | import de.otto.jlineup.exceptions.ValidationError; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | 9 | import static de.otto.jlineup.config.DeviceConfig.deviceConfigBuilder; 10 | import static de.otto.jlineup.config.JobConfig.jobConfigBuilder; 11 | import static de.otto.jlineup.config.UrlConfig.urlConfigBuilder; 12 | import static org.hamcrest.core.StringContains.containsString; 13 | import static org.junit.Assert.assertThat; 14 | import static org.junit.Assert.fail; 15 | 16 | public class JobConfigValidatorTest { 17 | 18 | @Test 19 | public void shouldDenyMobileEmulationWhenUsingOtherBrowserThanChrome() { 20 | // given 21 | JobConfig jobConfig = jobConfigBuilder() 22 | .addUrlConfig("someUrl", urlConfigBuilder() 23 | .withWindowWidths(null) 24 | .addDeviceConfig(deviceConfigBuilder() 25 | .withDeviceName("iPhone X") 26 | .build()) 27 | .build()) 28 | .withBrowser(Browser.Type.FIREFOX) 29 | .build(); 30 | 31 | // when 32 | try { 33 | JobConfigValidator.validateJobConfig(jobConfig); 34 | fail("Expected validation error"); 35 | 36 | // then 37 | } catch(ValidationError e) { 38 | assertThat(e.getMessage(), containsString("Mobile emulation is only supported by Chrome")); 39 | } 40 | } 41 | 42 | @Test 43 | public void shouldDenyMixedWindowWidthsAndDevices() { 44 | // given 45 | JobConfig jobConfig = jobConfigBuilder() 46 | .addUrlConfig("someUrl", urlConfigBuilder() 47 | .withWindowWidths(Arrays.asList(200,300,400)) 48 | .addDeviceConfig(deviceConfigBuilder() 49 | .withDeviceName("MOBILE") 50 | .build()) 51 | .build()) 52 | .withBrowser(Browser.Type.FIREFOX) 53 | .build(); 54 | 55 | // when 56 | try { 57 | JobConfigValidator.validateJobConfig(jobConfig); 58 | fail("Expected validation error"); 59 | 60 | // then 61 | } catch(ValidationError e) { 62 | assertThat(e.getMessage(), containsString("window-widths")); 63 | } 64 | } 65 | 66 | @Test 67 | public void shouldDenyUserAgentWhenSpecialDeviceNameIsSpecified() { 68 | // given 69 | JobConfig jobConfig = jobConfigBuilder() 70 | .addUrlConfig("someUrl", urlConfigBuilder() 71 | .withWindowWidths(null) 72 | .addDeviceConfig(deviceConfigBuilder() 73 | .withDeviceName("iPhone X") 74 | .withUserAgent("someUserAgent") 75 | .build()) 76 | .build()) 77 | .withBrowser(Browser.Type.CHROME) 78 | .build(); 79 | 80 | // when 81 | try { 82 | JobConfigValidator.validateJobConfig(jobConfig); 83 | fail("Expected validation error"); 84 | 85 | // then 86 | } catch(ValidationError e) { 87 | assertThat(e.getMessage(), containsString("overridden")); 88 | } 89 | } 90 | 91 | } --------------------------------------------------------------------------------