├── src
├── test
│ ├── resources
│ │ ├── de
│ │ │ └── retest
│ │ │ │ └── web
│ │ │ │ ├── AttributesConfigDeserializerTest
│ │ │ │ ├── empty.yaml
│ │ │ │ ├── all-html.yaml
│ │ │ │ ├── invalid-html.yaml
│ │ │ │ └── selected.yaml
│ │ │ │ ├── selenium
│ │ │ │ └── UnbreakableDriverIT.html
│ │ │ │ ├── it
│ │ │ │ ├── tab.html
│ │ │ │ ├── page.html
│ │ │ │ ├── pseudo-elements.css
│ │ │ │ ├── pseudo-element.html
│ │ │ │ ├── pseudo-element-changed.html
│ │ │ │ ├── pseudo-elements-changed.css
│ │ │ │ ├── RootElementPeerNullTest.html
│ │ │ │ └── TestHealerCssSelectorIT.html
│ │ │ │ └── AutoHealingTest.html
│ │ ├── retest
│ │ │ ├── recheck
│ │ │ │ ├── de.retest.web.it.TabIT
│ │ │ │ │ └── recheck.ignore
│ │ │ │ ├── de.retest.web.it.CenterIT
│ │ │ │ │ ├── center-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ └── html.png
│ │ │ │ │ └── center-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html.png
│ │ │ │ ├── de.retest.web.it.TestHealerCssSelectorIT
│ │ │ │ │ ├── setup.00.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ └── root.png
│ │ │ │ │ └── setup.01.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── root.png
│ │ │ │ ├── de.retest.web.it.ShowcaseIT
│ │ │ │ │ └── showcase-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ ├── html.png
│ │ │ │ │ │ └── html_006f66c7412bad4d92aaec4856af74d3f506bc2df3645f9f1ea953c444b028af.png
│ │ │ │ ├── de.retest.web.it.FormPageIT
│ │ │ │ │ └── form-page-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html.png
│ │ │ │ ├── de.retest.web.it.FramePageIT
│ │ │ │ │ ├── frame-page-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ └── html.png
│ │ │ │ │ └── frame-page-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html.png
│ │ │ │ ├── de.retest.web.it.SimplePageIT
│ │ │ │ │ ├── simple-page-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ ├── html.png
│ │ │ │ │ │ │ └── html_ccc95f1c9c6803a22f080f151bfacd67a737c35b1cb4f5a2f07b16d30105e204.png
│ │ │ │ │ └── simple-page-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html_43ab85c02685dcd082f87fef200b9586fd3945be3eac4b16d06d75fa3836136b.png
│ │ │ │ ├── de.retest.web.it.SpecialContainerIT
│ │ │ │ │ ├── special-container-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ ├── html.png
│ │ │ │ │ │ │ └── html_7b43151124a12deb468c88e608ba52949773935aec87fd9c24a4d88609b0155c.png
│ │ │ │ │ ├── special-container-FirefoxDriver.click.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ ├── html.png
│ │ │ │ │ │ │ └── html_7b43151124a12deb468c88e608ba52949773935aec87fd9c24a4d88609b0155c.png
│ │ │ │ │ ├── special-container-ChromeDriver.click.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ └── html_f5603547723e5f5c3b9979f8db49ad6b91389981770f6666898b686e5d882b53.png
│ │ │ │ │ └── special-container-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html_f5603547723e5f5c3b9979f8db49ad6b91389981770f6666898b686e5d882b53.png
│ │ │ │ ├── de.retest.web.it.SimpleUnbreakableSeleniumShowcaseIT
│ │ │ │ │ └── simple-showcase.index.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html.png
│ │ │ │ ├── de.retest.web.it.WikipediaIT
│ │ │ │ │ └── myWikipediaTest.characterization-testing-page.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html.png
│ │ │ │ ├── de.retest.web.it.RecheckRemoteWebElementIT
│ │ │ │ │ ├── simple-webelement-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ ├── email.png
│ │ │ │ │ │ │ └── input_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
│ │ │ │ │ ├── empty-article-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ └── article_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png
│ │ │ │ │ ├── empty-article-FirefoxDriver.open.recheck
│ │ │ │ │ │ ├── screenshot
│ │ │ │ │ │ │ └── article_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
│ │ │ │ │ │ └── retest.xml
│ │ │ │ │ ├── simple-webelement-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ └── input_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png
│ │ │ │ │ └── findElement-equals-driver-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
│ │ │ │ ├── de.retest.web.it.RecheckRemoteWebElementFailingIT
│ │ │ │ │ ├── select-element-FirefoxDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ │ ├── multi.png
│ │ │ │ │ │ │ └── select_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
│ │ │ │ │ └── select-element-ChromeDriver.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── select_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png
│ │ │ │ ├── de.retest.web.it.SimplePageDiffIT
│ │ │ │ │ └── testSimpleChange.open.recheck
│ │ │ │ │ │ └── screenshot
│ │ │ │ │ │ └── html_ec47e175cc5a136fbd93d9daadba64a38dd208426a378a091dfa892e70aa5180.png
│ │ │ │ └── de.retest.web.it.SimpleRetestIdShowcaseIT
│ │ │ │ │ └── simple-showcase.index.recheck
│ │ │ │ │ └── screenshot
│ │ │ │ │ └── html_c529e46f176dacc7a258ef25e8241cbc9dbcfd9e659538e5b9a023c973801af2.png
│ │ │ └── retest.properties
│ │ └── pages
│ │ │ ├── frame.html
│ │ │ ├── frame-diff.html
│ │ │ ├── showcase
│ │ │ └── assets
│ │ │ │ ├── de.png
│ │ │ │ ├── us.png
│ │ │ │ ├── logo.png
│ │ │ │ ├── tick.png
│ │ │ │ ├── one-click.png
│ │ │ │ ├── EXIST-Logo.png
│ │ │ │ ├── 1-on-1-assignment.png
│ │ │ │ ├── redundant-infos.png
│ │ │ │ ├── monkey_clever_laptop.png
│ │ │ │ ├── innovationsgutschein-logo.png
│ │ │ │ ├── BestOf_IT-Service_2016_170px.png
│ │ │ │ ├── emojione.min.css
│ │ │ │ ├── doubletaptogo.js
│ │ │ │ ├── init.js
│ │ │ │ ├── jquery.waypoints.inview.min.js
│ │ │ │ └── jquery.freeow.min.js
│ │ │ ├── wikipedia
│ │ │ ├── actual
│ │ │ │ └── assets
│ │ │ │ │ ├── enwiki-2x.png
│ │ │ │ │ ├── wikimedia-button.png
│ │ │ │ │ ├── 32px-Green_bug_and_broom.png
│ │ │ │ │ ├── poweredby_mediawiki_88x31.png
│ │ │ │ │ └── load_002.css
│ │ │ └── expected
│ │ │ │ └── assets
│ │ │ │ ├── enwiki-2x.png
│ │ │ │ ├── wikimedia-button.png
│ │ │ │ ├── 32px-Green_bug_and_broom.png
│ │ │ │ ├── poweredby_mediawiki_88x31.png
│ │ │ │ └── load_002.css
│ │ │ ├── single-element-diff.html
│ │ │ ├── frame-outer.html
│ │ │ ├── special-container.html
│ │ │ ├── frame-page.html
│ │ │ ├── centered.html
│ │ │ ├── covered-page.html
│ │ │ ├── simple-page.html
│ │ │ └── simple-page-diff.html
│ └── java
│ │ └── de
│ │ └── retest
│ │ └── web
│ │ ├── testutils
│ │ ├── SystemProperty.java
│ │ ├── SystemPropertyExtension.java
│ │ ├── PageFactory.java
│ │ └── WebDriverFactory.java
│ │ ├── selenium
│ │ ├── CounterCheckNamingStrategyTest.java
│ │ ├── AutocheckingRecheckDriverTest.java
│ │ ├── UnbreakableDriverTest.java
│ │ ├── AutocheckingWebElementIT.java
│ │ ├── UnbreakableDriverIT.java
│ │ ├── AutocheckingTargetLocatorTest.java
│ │ ├── css
│ │ │ └── RegexTransformerTest.java
│ │ ├── ByWhispererTest.java
│ │ └── WriteToResultWarningConsumerTest.java
│ │ ├── util
│ │ └── TextAttributeUtilTest.java
│ │ ├── meta
│ │ ├── driver
│ │ │ ├── capabilities
│ │ │ │ ├── BrowserMetadataProviderTest.java
│ │ │ │ ├── PlatformMetadataProviderTest.java
│ │ │ │ ├── CapabilityMetadataProviderTest.java
│ │ │ │ └── AllCapabilities.java
│ │ │ └── WebDriverMetadataProviderTest.java
│ │ ├── element
│ │ │ └── WebElementMetadataProviderTest.java
│ │ └── SeleniumMetadataProviderTest.java
│ │ ├── ScreenshotProviderTest.java
│ │ ├── mapping
│ │ ├── PathsToWebDataMappingTest.java
│ │ └── WebDataFilterTest.java
│ │ ├── screenshot
│ │ └── ViewportOnlyMinimalScreenshotTest.java
│ │ ├── it
│ │ ├── CenterIT.java
│ │ ├── FramePageIT.java
│ │ ├── SimplePageIT.java
│ │ ├── ShowcaseIT.java
│ │ ├── FormPageIT.java
│ │ ├── SpecialContainerIT.java
│ │ ├── WikipediaIT.java
│ │ ├── SimplePageDiffIT.java
│ │ ├── TabIT.java
│ │ ├── SimpleAutocheckingDriverShowcaseIT.java
│ │ ├── SingleElementIT.java
│ │ ├── RootElementPeerNullIT.java
│ │ ├── SimpleRecheckShowcaseIT.java
│ │ ├── RecheckRemoteWebElementIT.java
│ │ ├── RecheckRemoteWebElementFailingIT.java
│ │ └── SimpleRetestIdShowcaseIT.java
│ │ ├── RecheckWebImplIT.java
│ │ └── PeerConverterTest.java
├── main
│ ├── resources
│ │ └── META-INF
│ │ │ └── services
│ │ │ └── de.retest.recheck.RecheckAdapter
│ └── java
│ │ └── de
│ │ └── retest
│ │ └── web
│ │ ├── selenium
│ │ ├── css
│ │ │ ├── Transformer.java
│ │ │ ├── PredicateFactory.java
│ │ │ ├── Selector.java
│ │ │ ├── RegexTransformer.java
│ │ │ ├── PredicateBuilder.java
│ │ │ └── DefaultSelectors.java
│ │ ├── QualifiedElementWarning.java
│ │ ├── ImplicitDriverWrapper.java
│ │ ├── NoElementWithReTestIdFoundException.java
│ │ ├── NoElementWithHighEnoughMatchFoundException.java
│ │ ├── RecheckDriver.java
│ │ ├── CounterCheckNamingStrategy.java
│ │ ├── AutocheckingNavigationWrapper.java
│ │ ├── AutocheckingCheckNamingStrategy.java
│ │ ├── AutocheckingTargetLocator.java
│ │ ├── WriteToResultWarningConsumer.java
│ │ └── By.java
│ │ ├── ConversionException.java
│ │ ├── screenshot
│ │ ├── NoScreenshot.java
│ │ ├── ViewportOnlyScreenshot.java
│ │ ├── ScreenshotProvider.java
│ │ ├── FullPageScreenshot.java
│ │ ├── ViewportOnlyMinimalScreenshot.java
│ │ └── ScreenshotProviders.java
│ │ ├── util
│ │ └── TextAttributeUtil.java
│ │ ├── meta
│ │ ├── SeleniumMetadata.java
│ │ ├── driver
│ │ │ ├── capabilities
│ │ │ │ ├── BrowserMetadataProvider.java
│ │ │ │ ├── PlatformMetadataProvider.java
│ │ │ │ └── CapabilityMetadataProvider.java
│ │ │ └── WebDriverMetadataProvider.java
│ │ ├── element
│ │ │ └── WebElementMetadataProvider.java
│ │ └── SeleniumMetadataProvider.java
│ │ ├── mapping
│ │ ├── WebDataFilter.java
│ │ └── PathsToWebDataMapping.java
│ │ ├── AttributesUtil.java
│ │ ├── RecheckWebProperties.java
│ │ ├── RootElementPeer.java
│ │ └── RecheckWebImpl.java
└── eclipse
│ └── retest GUI recheck-web Java 1.8.launch
├── .retest
├── retest.properties
├── recheck.ignore.js
├── filter
│ └── recheck-pseudo-elements.filter
└── recheck.ignore
├── CONTRIBUTING.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── .github
├── dependabot.yml
├── pull_request_template.md
├── ISSUE_TEMPLATE
│ ├── feature.md
│ └── bug.md
└── workflows
│ ├── deploy-release.yml
│ ├── release-hotfix-start.yml
│ ├── release-feature-start.yml
│ ├── release-hotfix-finish.yml
│ ├── release-feature-finish.yml
│ ├── build-project.yml
│ └── release-beta.yml
├── .gitattributes
└── .mergify.yml
/src/test/resources/de/retest/web/AttributesConfigDeserializerTest/empty.yaml:
--------------------------------------------------------------------------------
1 | htmlAttributes:
2 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/AttributesConfigDeserializerTest/all-html.yaml:
--------------------------------------------------------------------------------
1 | htmlAttributes: all
2 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/de.retest.recheck.RecheckAdapter:
--------------------------------------------------------------------------------
1 | de.retest.web.RecheckSeleniumAdapter
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/AttributesConfigDeserializerTest/invalid-html.yaml:
--------------------------------------------------------------------------------
1 | htmlAttributes: invalid
2 |
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.TabIT/recheck.ignore:
--------------------------------------------------------------------------------
1 | change=inserted
2 | change=deleted
3 |
--------------------------------------------------------------------------------
/src/test/resources/pages/frame.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/frame.html
--------------------------------------------------------------------------------
/.retest/retest.properties:
--------------------------------------------------------------------------------
1 | de.retest.recheck.ignore.attributes=absolute-outline
2 | de.retest.recheck.web.screenshot.provider=none
3 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | The ***retest*** Contributing Guidelines can be found at https://docs.retest.de/contributing.
4 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/AttributesConfigDeserializerTest/selected.yaml:
--------------------------------------------------------------------------------
1 | htmlAttributes:
2 | - tic
3 | - tac
4 | - toe
5 |
--------------------------------------------------------------------------------
/src/test/resources/pages/frame-diff.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/frame-diff.html
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/de.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/de.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/us.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/us.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/logo.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/tick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/tick.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse.
2 | .classpath
3 | .project
4 | .settings/
5 |
6 | # IntelliJ.
7 | .idea/
8 | *.iml
9 |
10 | # Maven.
11 | /target/
12 |
13 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | The ***retest*** Code of Conduct can be found at https://docs.retest.de/code-of-conduct.
4 |
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/one-click.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/one-click.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/EXIST-Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/EXIST-Logo.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/1-on-1-assignment.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/1-on-1-assignment.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/redundant-infos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/redundant-infos.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/actual/assets/enwiki-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/actual/assets/enwiki-2x.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/expected/assets/enwiki-2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/expected/assets/enwiki-2x.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/monkey_clever_laptop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/monkey_clever_laptop.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/actual/assets/wikimedia-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/actual/assets/wikimedia-button.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/innovationsgutschein-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/innovationsgutschein-logo.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/expected/assets/wikimedia-button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/expected/assets/wikimedia-button.png
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/BestOf_IT-Service_2016_170px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/showcase/assets/BestOf_IT-Service_2016_170px.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/actual/assets/32px-Green_bug_and_broom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/actual/assets/32px-Green_bug_and_broom.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/actual/assets/poweredby_mediawiki_88x31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/actual/assets/poweredby_mediawiki_88x31.png
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/expected/assets/32px-Green_bug_and_broom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/expected/assets/32px-Green_bug_and_broom.png
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/css/Transformer.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium.css;
2 |
3 | @FunctionalInterface
4 | interface Transformer {
5 |
6 | Selector transform( String selector );
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/resources/pages/wikipedia/expected/assets/poweredby_mediawiki_88x31.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/pages/wikipedia/expected/assets/poweredby_mediawiki_88x31.png
--------------------------------------------------------------------------------
/src/test/resources/pages/single-element-diff.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | create( String attribute );
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/QualifiedElementWarning.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import de.retest.recheck.ui.diff.ElementIdentificationWarning;
4 | import lombok.Value;
5 |
6 | @Value
7 | public class QualifiedElementWarning {
8 |
9 | private String retestId;
10 | private String attributeKey;
11 | private ElementIdentificationWarning warning;
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page
6 |
7 |
8 | New Tab
9 | New Window
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.ShowcaseIT/showcase-FirefoxDriver.open.recheck/screenshot/html_006f66c7412bad4d92aaec4856af74d3f506bc2df3645f9f1ea953c444b028af.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.ShowcaseIT/showcase-FirefoxDriver.open.recheck/screenshot/html_006f66c7412bad4d92aaec4856af74d3f506bc2df3645f9f1ea953c444b028af.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SimplePageDiffIT/testSimpleChange.open.recheck/screenshot/html_ec47e175cc5a136fbd93d9daadba64a38dd208426a378a091dfa892e70aa5180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SimplePageDiffIT/testSimpleChange.open.recheck/screenshot/html_ec47e175cc5a136fbd93d9daadba64a38dd208426a378a091dfa892e70aa5180.png
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/ConversionException.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | public class ConversionException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public ConversionException( final String msg ) {
8 | super( msg );
9 | }
10 |
11 | public ConversionException( final String msg, final Exception e ) {
12 | super( msg, e );
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SimplePageIT/simple-page-ChromeDriver.open.recheck/screenshot/html_43ab85c02685dcd082f87fef200b9586fd3945be3eac4b16d06d75fa3836136b.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SimplePageIT/simple-page-ChromeDriver.open.recheck/screenshot/html_43ab85c02685dcd082f87fef200b9586fd3945be3eac4b16d06d75fa3836136b.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SimplePageIT/simple-page-FirefoxDriver.open.recheck/screenshot/html_ccc95f1c9c6803a22f080f151bfacd67a737c35b1cb4f5a2f07b16d30105e204.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SimplePageIT/simple-page-FirefoxDriver.open.recheck/screenshot/html_ccc95f1c9c6803a22f080f151bfacd67a737c35b1cb4f5a2f07b16d30105e204.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SimpleRetestIdShowcaseIT/simple-showcase.index.recheck/screenshot/html_c529e46f176dacc7a258ef25e8241cbc9dbcfd9e659538e5b9a023c973801af2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SimpleRetestIdShowcaseIT/simple-showcase.index.recheck/screenshot/html_c529e46f176dacc7a258ef25e8241cbc9dbcfd9e659538e5b9a023c973801af2.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-ChromeDriver.click.recheck/screenshot/html_f5603547723e5f5c3b9979f8db49ad6b91389981770f6666898b686e5d882b53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-ChromeDriver.click.recheck/screenshot/html_f5603547723e5f5c3b9979f8db49ad6b91389981770f6666898b686e5d882b53.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-ChromeDriver.open.recheck/screenshot/html_f5603547723e5f5c3b9979f8db49ad6b91389981770f6666898b686e5d882b53.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-ChromeDriver.open.recheck/screenshot/html_f5603547723e5f5c3b9979f8db49ad6b91389981770f6666898b686e5d882b53.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-FirefoxDriver.open.recheck/screenshot/html_7b43151124a12deb468c88e608ba52949773935aec87fd9c24a4d88609b0155c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-FirefoxDriver.open.recheck/screenshot/html_7b43151124a12deb468c88e608ba52949773935aec87fd9c24a4d88609b0155c.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-FirefoxDriver.click.recheck/screenshot/html_7b43151124a12deb468c88e608ba52949773935aec87fd9c24a4d88609b0155c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.SpecialContainerIT/special-container-FirefoxDriver.click.recheck/screenshot/html_7b43151124a12deb468c88e608ba52949773935aec87fd9c24a4d88609b0155c.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/empty-article-ChromeDriver.open.recheck/screenshot/article_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/empty-article-ChromeDriver.open.recheck/screenshot/article_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/empty-article-FirefoxDriver.open.recheck/screenshot/article_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/empty-article-FirefoxDriver.open.recheck/screenshot/article_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/simple-webelement-ChromeDriver.open.recheck/screenshot/input_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/simple-webelement-ChromeDriver.open.recheck/screenshot/input_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/simple-webelement-FirefoxDriver.open.recheck/screenshot/input_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/simple-webelement-FirefoxDriver.open.recheck/screenshot/input_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementFailingIT/select-element-ChromeDriver.open.recheck/screenshot/select_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementFailingIT/select-element-ChromeDriver.open.recheck/screenshot/select_b0398bd8a34b4b9128404e8c7b6ecb99d35238103cddf4bd8bb30363dbca8c84.png
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementFailingIT/select-element-FirefoxDriver.open.recheck/screenshot/select_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementFailingIT/select-element-FirefoxDriver.open.recheck/screenshot/select_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.css text encoding=utf-8 eol=lf
2 | *.html text encoding=utf-8 eol=lf
3 | *.ignore text encoding=utf-8 eol=lf
4 | *.java text encoding=utf-8 eol=lf
5 | *.js text encoding=utf-8 eol=lf
6 | *.md text encoding=utf-8 eol=lf
7 | *.properties text encoding=utf-8 eol=lf
8 | *.sh text encoding=utf-8 eol=lf
9 | *.xml text encoding=utf-8 eol=lf
10 | *.yaml text encoding=utf-8 eol=lf
11 | *.yml text encoding=utf-8 eol=lf
12 | *.cmd text encoding=utf-8 eol=crlf
13 |
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/findElement-equals-driver-FirefoxDriver.open.recheck/screenshot/html_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/retest/recheck-web/HEAD/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/findElement-equals-driver-FirefoxDriver.open.recheck/screenshot/html_55bf18063eed7a040d95559c1a8689df5118c2d589738e9d491872f3b9153f2d.png
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/ImplicitDriverWrapper.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import org.openqa.selenium.WebDriver;
4 | import org.openqa.selenium.WrapsDriver;
5 |
6 | import lombok.AccessLevel;
7 | import lombok.RequiredArgsConstructor;
8 |
9 | @RequiredArgsConstructor( access = AccessLevel.PACKAGE, staticName = "of" )
10 | public class ImplicitDriverWrapper implements WrapsDriver {
11 |
12 | private final WebDriver wrapped;
13 |
14 | @Override
15 | public WebDriver getWrappedDriver() {
16 | return wrapped;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/NoElementWithReTestIdFoundException.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | public class NoElementWithReTestIdFoundException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | private final String retestId;
8 |
9 | public NoElementWithReTestIdFoundException( final String retestId ) {
10 | super( "No element with retest ID '" + retestId + "' found!" );
11 | this.retestId = retestId;
12 | }
13 |
14 | public String getRetestId() {
15 | return retestId;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/testutils/SystemProperty.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.testutils;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 |
10 | @Retention( RetentionPolicy.RUNTIME )
11 | @Target( ElementType.METHOD )
12 | @ExtendWith( SystemPropertyExtension.class )
13 | public @interface SystemProperty {
14 |
15 | String key();
16 |
17 | String value() default "";
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/pseudo-elements.css:
--------------------------------------------------------------------------------
1 | h1::before {
2 | content: "This is an invisible text before!";
3 | color: red;
4 | background-color: green;
5 | }
6 |
7 | h1::after {
8 | content: "This is an invisible text after!";
9 | color: red;
10 | background-color: green;
11 | }
12 |
13 | h1 {
14 | color: green;
15 | background-color: red;
16 | }
17 |
18 | h1#hidden {
19 | display: none;
20 | }
21 |
22 | li::marker {
23 | content: "marker > ";
24 | color: purple;
25 | }
26 |
27 | p::first-line {
28 | color: #abc;
29 | }
30 |
31 | p::first-letter {
32 | color: #bca;
33 | }
--------------------------------------------------------------------------------
/src/test/resources/pages/frame-outer.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | Multi Frame
8 |
9 |
10 |
11 |
12 |
13 |
14 | Diese Seite verwendet Frames. Frames werden von Ihrem Browser aber nicht unterstützt.
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/pseudo-element.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | You can use the ::first-line pseudo-element to add a special effect to the first line of a text. Some more text. And even more, and more, and more, and more, and more, and more, and more, and more, and more, and more, and more, and more.
9 |
10 | This is a heading
11 |
12 | This is a hidden heading
13 |
14 |
15 | First item
16 | Second item
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/screenshot/NoScreenshot.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import java.awt.image.BufferedImage;
4 |
5 | import org.openqa.selenium.WebDriver;
6 | import org.openqa.selenium.WebElement;
7 |
8 | /**
9 | * Does not take any screenshot at all. Increases the performance. Cannot be set globally.
10 | */
11 | public class NoScreenshot implements ScreenshotProvider {
12 |
13 | @Override
14 | public BufferedImage shoot( final WebDriver driver ) {
15 | return null;
16 | }
17 |
18 | @Override
19 | public BufferedImage shoot( final WebDriver driver, final WebElement element ) {
20 | return null;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/pseudo-element-changed.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | You can use the ::first-line pseudo-element to add a special effect to the first line of a text. Some more text. And even more, and more, and more, and more, and more, and more, and more, and more, and more, and more, and more, and more.
9 |
10 | This is a heading
11 |
12 | This is a hidden heading
13 |
14 |
15 | First item
16 | Second item
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/pseudo-elements-changed.css:
--------------------------------------------------------------------------------
1 | h1::before {
2 | content: "This is a changed invisible text using a before pseudo element!";
3 | color: orange;
4 | background-color: blue;
5 | }
6 |
7 | h1::after {
8 | content: "This is a changed invisible text using an after pseudo element!";
9 | color: orange;
10 | background-color: blue;
11 | }
12 |
13 | h1 {
14 | color: blue;
15 | background-color: orange;
16 | }
17 |
18 | h1#hidden {
19 |
20 | }
21 |
22 | li::marker {
23 | content: "changed marker > ";
24 | color: cyan;
25 | }
26 |
27 | p::first-line {
28 | color: #def;
29 | }
30 |
31 | p::first-letter {
32 | color: #efd;
33 | }
34 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/CounterCheckNamingStrategyTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class CounterCheckNamingStrategyTest {
8 |
9 | private final CounterCheckNamingStrategy cut = new CounterCheckNamingStrategy();
10 |
11 | @Test
12 | void makeUnique_should_append_unique_prefix() {
13 | assertThat( cut.getUniqueCheckName( "someString", null ) ).isEqualTo( "00" );
14 | assertThat( cut.getUniqueCheckName( "someOtherString", null ) ).isEqualTo( "01" );
15 | assertThat( cut.getUniqueCheckName( "someString", null ) ).isEqualTo( "02" );
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/AutocheckingRecheckDriverTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.mock;
5 |
6 | import org.junit.jupiter.api.BeforeEach;
7 | import org.junit.jupiter.api.Test;
8 | import org.openqa.selenium.remote.RemoteWebDriver;
9 |
10 | class AutocheckingRecheckDriverTest {
11 |
12 | AutocheckingRecheckDriver cut;
13 |
14 | @BeforeEach
15 | void setUp() {
16 | cut = new AutocheckingRecheckDriver( mock( RemoteWebDriver.class ) );
17 | }
18 |
19 | @Test
20 | void switchTo_should_create_autochecking_locator() {
21 | assertThat( cut.switchTo() ).isInstanceOf( AutocheckingTargetLocator.class );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/util/TextAttributeUtilTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.util;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.api.Test;
6 |
7 | class TextAttributeUtilTest {
8 |
9 | @Test
10 | void create_should_return_a_text_attribute_without_formatting() {
11 | final String text = "foo \n bar";
12 | final String expected = text.replaceAll( "\\s+", " " );
13 | assertThat( TextAttributeUtil.createTextAttribute( "", text ).getValue() ).isEqualTo( expected );
14 | }
15 |
16 | @Test
17 | void create_should_return_text_attribute_with_formatting() {
18 | final String text = "foo \n bar";
19 | assertThat( TextAttributeUtil.createTextAttribute( "/" + TextAttributeUtil.PRE_ELEMENT, text ).getValue() )
20 | .isEqualTo( text );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/css/Selector.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium.css;
2 |
3 | import java.util.function.Predicate;
4 |
5 | import de.retest.recheck.ui.descriptors.Element;
6 | import lombok.AccessLevel;
7 | import lombok.Getter;
8 | import lombok.RequiredArgsConstructor;
9 |
10 | @Getter
11 | @RequiredArgsConstructor( access = AccessLevel.PRIVATE )
12 | class Selector {
13 |
14 | private final String remainingSelector;
15 | private final Predicate predicate;
16 |
17 | static Selector supported( final String remainingSelector, final Predicate predicate ) {
18 | return new Selector( remainingSelector, predicate );
19 | }
20 |
21 | static Selector unsupported( final String remainingSelector ) {
22 | return new Selector( remainingSelector, e -> false );
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/resources/pages/special-container.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Welcome to the Test of Recheck-web
6 |
7 |
8 |
9 |
10 |
11 | Das ist ein Text!
12 |
13 | Und
das auch.
14 |
15 |
16 | Change
17 |
18 |
24 |
25 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/NoElementWithHighEnoughMatchFoundException.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import de.retest.recheck.RecheckProperties;
4 | import de.retest.recheck.ui.descriptors.Element;
5 |
6 | public class NoElementWithHighEnoughMatchFoundException extends RuntimeException {
7 |
8 | private static final long serialVersionUID = 1L;
9 |
10 | public NoElementWithHighEnoughMatchFoundException( final Element resultFromExpected ) {
11 | super( "The element " + resultFromExpected + " from the Golden Master could not be matched with confidence > "
12 | + RecheckProperties.getInstance().elementMatchThreshold()
13 | + ". To change the required minimal confidence for a match, please set property '"
14 | + RecheckProperties.ELEMENT_MATCH_THRESHOLD_PROPERTY_KEY + "'." );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/util/TextAttributeUtil.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.util;
2 |
3 | import static de.retest.web.AttributesUtil.TEXT;
4 |
5 | import de.retest.recheck.ui.descriptors.TextAttribute;
6 |
7 | public class TextAttributeUtil {
8 |
9 | private TextAttributeUtil() {}
10 |
11 | public static final String PRE_ELEMENT = "/pre[";
12 |
13 | public static TextAttribute createTextAttribute( final String path, final String text ) {
14 | return isPreContained( path ) ? new TextAttribute( TEXT, text )
15 | : new TextAttribute( TEXT, removeFormatting( text ) );
16 | }
17 |
18 | private static boolean isPreContained( final String path ) {
19 | return path.toLowerCase().contains( PRE_ELEMENT );
20 | }
21 |
22 | private static String removeFormatting( final String text ) {
23 | return text.replaceAll( "\\s+", " " ).trim();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/SeleniumMetadata.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta;
2 |
3 | import de.retest.recheck.meta.global.OSMetadataProvider;
4 |
5 | public final class SeleniumMetadata {
6 |
7 | // Generic
8 |
9 | public static final String CHECK_TYPE = "check.type";
10 |
11 | // Driver
12 |
13 | public static final String BROWSER_NAME = "browser.name";
14 | public static final String BROWSER_VERSION = "browser.version";
15 |
16 | public static final String OS_NAME = OSMetadataProvider.OS_NAME;
17 | public static final String OS_VERSION = OSMetadataProvider.OS_VERSION;
18 | public static final String OS_ARCH = OSMetadataProvider.OS_ARCH;
19 |
20 | public static final String DRIVER_TYPE = "driver.type";
21 |
22 | public static final String WINDOW_WIDTH = "window.width";
23 | public static final String WINDOW_HEIGHT = "window.height";
24 |
25 | public static final String URL = "url";
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/driver/capabilities/BrowserMetadataProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.params.ParameterizedTest;
6 | import org.junit.jupiter.params.provider.MethodSource;
7 | import org.openqa.selenium.Capabilities;
8 |
9 | import de.retest.web.meta.SeleniumMetadata;
10 |
11 | class BrowserMetadataProviderTest {
12 |
13 | @ParameterizedTest
14 | @MethodSource( "de.retest.web.meta.driver.capabilities.AllCapabilities#capabilities" )
15 | void retrieve_should_gather_browser_name_and_version( final Capabilities capabilities ) throws Exception {
16 | final BrowserMetadataProvider cut = new BrowserMetadataProvider( capabilities );
17 |
18 | assertThat( cut.retrieve() ) //
19 | .containsKeys( SeleniumMetadata.BROWSER_NAME, SeleniumMetadata.BROWSER_VERSION );
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/driver/capabilities/BrowserMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import org.openqa.selenium.Capabilities;
7 |
8 | import de.retest.recheck.meta.MetadataProvider;
9 | import de.retest.web.meta.SeleniumMetadata;
10 | import lombok.AccessLevel;
11 | import lombok.RequiredArgsConstructor;
12 |
13 | @RequiredArgsConstructor( access = AccessLevel.PACKAGE )
14 | public final class BrowserMetadataProvider implements MetadataProvider {
15 |
16 | private final Capabilities capabilities;
17 |
18 | @Override
19 | public Map retrieve() {
20 | final Map map = new HashMap<>();
21 |
22 | map.put( SeleniumMetadata.BROWSER_NAME, capabilities.getBrowserName() );
23 | map.put( SeleniumMetadata.BROWSER_VERSION, capabilities.getVersion() );
24 |
25 | return map;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/driver/capabilities/PlatformMetadataProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 |
5 | import org.junit.jupiter.params.ParameterizedTest;
6 | import org.junit.jupiter.params.provider.MethodSource;
7 | import org.openqa.selenium.Capabilities;
8 |
9 | import de.retest.web.meta.SeleniumMetadata;
10 | import lombok.extern.slf4j.Slf4j;
11 |
12 | @Slf4j
13 | class PlatformMetadataProviderTest {
14 |
15 | @ParameterizedTest
16 | @MethodSource( "de.retest.web.meta.driver.capabilities.AllCapabilities#capabilities" )
17 | void retrieve_should_properly_resolve_platform( final Capabilities capabilities ) throws Exception {
18 | final PlatformMetadataProvider cut = new PlatformMetadataProvider( capabilities );
19 |
20 | assertThat( cut.retrieve() ) //
21 | .containsKeys( SeleniumMetadata.OS_NAME );
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/RecheckDriver.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import org.openqa.selenium.remote.RemoteWebDriver;
4 |
5 | import de.retest.recheck.RecheckOptions;
6 | import de.retest.web.RecheckWebOptions;
7 |
8 | /**
9 | * Extends both {@link AutocheckingRecheckDriver} and {@link UnbreakableDriver} to combine all recheck-web features. Use
10 | * this class if you automatically want to incorporate new features without changing your recheck-web driver
11 | * implementation explicitly.
12 | */
13 | public class RecheckDriver extends AutocheckingRecheckDriver {
14 |
15 | public RecheckDriver( final RemoteWebDriver wrapped ) {
16 | super( wrapped );
17 | }
18 |
19 | public RecheckDriver( final RemoteWebDriver wrapped, final RecheckOptions options ) {
20 | super( wrapped, options );
21 | }
22 |
23 | public RecheckDriver( final RemoteWebDriver wrapped, final RecheckWebOptions options ) {
24 | super( wrapped, options );
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/resources/pages/frame-page.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Diese Seite verwendet Frames. Frames werden von Ihrem Browser aber nicht unterstützt.
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/AutoHealingTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | AutoHealingTest
9 |
10 |
11 |
12 | Click to change!
13 |
14 |
15 |
16 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/CounterCheckNamingStrategy.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import org.openqa.selenium.WebElement;
4 |
5 | /**
6 | * This naming strategy simply counts the checks and returns a two-digit string of the current check, starting with 0.
7 | * This is robust to changes of the target and params, but fails miserably if steps are inserted, as this will affect
8 | * all subsequent checks. Also, this is somewhat humanly unreadable.
9 | */
10 | public class CounterCheckNamingStrategy implements AutocheckingCheckNamingStrategy {
11 |
12 | private int stepCounter = 0;
13 |
14 | @Override
15 | public String getUniqueCheckName( final String action, final WebElement target, final Object... params ) {
16 | return getUniqueCheckName( action );
17 | }
18 |
19 | @Override
20 | public String getUniqueCheckName( final String action ) {
21 | return String.format( "%02d", stepCounter++ );
22 | }
23 |
24 | @Override
25 | public void nextTest() {
26 | stepCounter = 0;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/screenshot/ViewportOnlyScreenshot.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import java.awt.image.BufferedImage;
4 |
5 | import org.openqa.selenium.WebDriver;
6 | import org.openqa.selenium.WebElement;
7 |
8 | import com.assertthat.selenium_shutterbug.core.Shutterbug;
9 |
10 | /**
11 | * The default implementation that takes a screenshot of only the viewport visible to the user from the top page. In
12 | * contrast to {@link ViewportOnlyMinimalScreenshot}, images are not resized.
13 | */
14 | public class ViewportOnlyScreenshot implements ScreenshotProvider {
15 |
16 | private static final boolean USE_DEVICE_PIXEL_RATIO = true;
17 |
18 | @Override
19 | public BufferedImage shoot( final WebDriver driver ) {
20 | return Shutterbug.shootPage( driver, USE_DEVICE_PIXEL_RATIO ).getImage();
21 | }
22 |
23 | @Override
24 | public BufferedImage shoot( final WebDriver driver, final WebElement element ) {
25 | return Shutterbug.shootElement( driver, element, USE_DEVICE_PIXEL_RATIO ).getImage();
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/.retest/recheck.ignore.js:
--------------------------------------------------------------------------------
1 | var baseUrl = /http[s]?:\/\/[\w.:\d\-]*/;
2 | var fontFamilies = [ [ "system-ui", "Arial" ], [ "-apple-system", "sans-serif" ] ];
3 |
4 | function contains(array, key) {
5 | for (var i = 0; i < array.length; i++) {
6 | if (key == array[i]) {
7 | return true;
8 | }
9 | }
10 | return false;
11 | }
12 |
13 | function matches(element, diff) {
14 | if (diff == null) {
15 | return false;
16 | }
17 | if (diff.key == "opacity") {
18 | return (Math.abs(diff.expected - diff.actual) <= 10);
19 | }
20 | if (diff.key == "font-family") {
21 | for (var i = 0; i < fontFamilies.length; i++) {
22 | if (contains(fontFamilies[i], diff.expected)) {
23 | return contains(fontFamilies[i], diff.actual);
24 | }
25 | }
26 | }
27 | if (diff.expected != null && diff.actual != null) {
28 | expected = new String(diff.expected);
29 | actual = new String(diff.actual);
30 | cleanExpected = expected.replace(baseUrl, '');
31 | cleanActual = actual.replace(baseUrl, '');
32 | return cleanExpected === cleanActual;
33 | }
34 | return false;
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/resources/pages/centered.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
33 |
34 |
35 |
36 | text
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | *Before submission, please check that ...*
2 |
3 | - [ ] the code follows the [Clean Code](https://clean-code-developer.com/) guidelines.
4 | - [ ] the necessary tests are either created or updated.
5 | - [ ] all status checks (GitHub Actions, SonarCloud, etc.) pass.
6 | - [ ] your commit history is cleaned up.
7 | - [ ] you updated the changelog.
8 | - [ ] you updated necessary documentation within [retest/docs](https://github.com/retest/docs).
9 |
10 |
11 |
12 | ## Description
13 |
14 | > TL;DR
15 |
16 |
17 |
18 | ### State of this PR
19 |
20 |
21 |
22 | ### Additional Context
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/ScreenshotProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.mock;
5 | import static org.mockito.Mockito.when;
6 |
7 | import org.junit.jupiter.api.Test;
8 | import org.mockito.Mockito;
9 | import org.openqa.selenium.chrome.ChromeDriver;
10 | import org.openqa.selenium.remote.CommandExecutor;
11 | import org.openqa.selenium.remote.RemoteWebDriver;
12 |
13 | import de.retest.web.screenshot.NoScreenshot;
14 | import de.retest.web.screenshot.ScreenshotProviders;
15 |
16 | class ScreenshotProviderTest {
17 |
18 | @Test
19 | void failingDriver_should_return_null() {
20 | final RemoteWebDriver exceptionCausingDriver = mock( ChromeDriver.class );
21 | when( exceptionCausingDriver.executeScript( Mockito.anyString() ) ).thenReturn( 1.0 );
22 | when( exceptionCausingDriver.getCommandExecutor() ).thenReturn( mock( CommandExecutor.class ) );
23 |
24 | assertThat( ScreenshotProviders.shoot( exceptionCausingDriver, null, new NoScreenshot() ) ).isNull();
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/mapping/PathsToWebDataMappingTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.mapping;
2 |
3 | import static java.util.Arrays.asList;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import java.util.ArrayList;
7 | import java.util.HashMap;
8 | import java.util.List;
9 |
10 | import org.junit.jupiter.api.Test;
11 |
12 | class PathsToWebDataMappingTest {
13 |
14 | @Test
15 | void should_have_shortest_xpath_as_root() {
16 | final List> mapping = new ArrayList<>();
17 | mapping.add( asList( "//html[1]", new HashMap<>() ) );
18 | mapping.add( asList( "//html[1]/body[1]", new HashMap<>() ) );
19 | mapping.add( asList( "//html[1]/body[1]/div[1]", new HashMap<>() ) );
20 |
21 | final PathsToWebDataMapping cut1 = new PathsToWebDataMapping( mapping );
22 | assertThat( cut1.getRootPath() ).isEqualTo( "//html[1]" );
23 |
24 | final PathsToWebDataMapping cut2 = new PathsToWebDataMapping( "//html[1]/body[1]/div[1]/iframe[1]", mapping );
25 | assertThat( cut2.getRootPath() ).isEqualTo( "//html[1]/body[1]/div[1]/iframe[1]/html[1]" );
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/doubletaptogo.js:
--------------------------------------------------------------------------------
1 | /*
2 | AUTHOR: Osvaldas Valutis, www.osvaldas.info
3 | */
4 |
5 |
6 |
7 | ;(function( $, window, document, undefined )
8 | {
9 | $.fn.doubleTapToGo = function( params )
10 | {
11 | if( !( 'ontouchstart' in window ) &&
12 | !navigator.msMaxTouchPoints &&
13 | !navigator.userAgent.toLowerCase().match( /windows phone os 7/i ) ) return false;
14 |
15 | this.each( function()
16 | {
17 | var curItem = false;
18 |
19 | $( this ).on( 'click', function( e )
20 | {
21 | var item = $( this );
22 | if( item[ 0 ] != curItem[ 0 ] )
23 | {
24 | e.preventDefault();
25 | curItem = item;
26 | }
27 | });
28 |
29 | $( document ).on( 'click touchstart MSPointerDown', function( e )
30 | {
31 | var resetItem = true,
32 | parents = $( e.target ).parents();
33 |
34 | for( var i = 0; i < parents.length; i++ )
35 | if( parents[ i ] == curItem[ 0 ] )
36 | resetItem = false;
37 |
38 | if( resetItem )
39 | curItem = false;
40 | });
41 | });
42 | return this;
43 | };
44 | })( jQuery, window, document );
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/mapping/WebDataFilter.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.mapping;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | // TODO This should be part of recheck and somehow use RecheckAdapter#convert to filter the result.
8 | public class WebDataFilter {
9 |
10 | /**
11 | * Tags that will not be ignored even if they are not shown.
12 | */
13 | static final Set specialTags = new HashSet<>( Arrays.asList( "meta", "option", "title" ) );
14 |
15 | private WebDataFilter() {}
16 |
17 | public static boolean shouldIgnore( final WebData webData ) {
18 | return isNotShown( webData ) && isNotPseudo( webData ) && isNotSpecialTag( webData );
19 | }
20 |
21 | private static boolean isNotShown( final WebData webData ) {
22 | return !webData.isShown();
23 | }
24 |
25 | private static boolean isNotPseudo( final WebData webData ) {
26 | return !webData.isPseudo();
27 | }
28 |
29 | private static boolean isNotSpecialTag( final WebData webData ) {
30 | final String lowerCaseTag = webData.getTag().toLowerCase();
31 | return !specialTags.contains( lowerCaseTag );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/screenshot/ViewportOnlyMinimalScreenshotTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import static de.retest.web.screenshot.ViewportOnlyMinimalScreenshot.DEFAULT_WANTED_WIDTH_PX;
4 | import static org.assertj.core.api.Assertions.assertThat;
5 |
6 | import java.awt.image.BufferedImage;
7 |
8 | import org.junit.jupiter.api.Test;
9 |
10 | class ViewportOnlyMinimalScreenshotTest {
11 |
12 | @Test
13 | void image_should_be_downscaled_to_maxwidth() {
14 | final BufferedImage big = new BufferedImage( 3000, 3000, BufferedImage.TYPE_INT_RGB );
15 | final BufferedImage result = ViewportOnlyMinimalScreenshot.resizeImage( big );
16 |
17 | assertThat( result.getWidth() ).isEqualTo( DEFAULT_WANTED_WIDTH_PX );
18 | assertThat( result.getHeight() ).isEqualTo( DEFAULT_WANTED_WIDTH_PX );
19 | }
20 |
21 | @Test
22 | void small_image_should_be_unaffected() {
23 | final BufferedImage small = new BufferedImage( 300, 300, BufferedImage.TYPE_INT_RGB );
24 | final BufferedImage result = ViewportOnlyMinimalScreenshot.resizeImage( small );
25 |
26 | assertThat( result.getWidth() ).isEqualTo( 300 );
27 | assertThat( result.getHeight() ).isEqualTo( 300 );
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/CenterIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static de.retest.web.testutils.PageFactory.page;
4 | import static de.retest.web.testutils.PageFactory.Page.CENTER;
5 |
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.Recheck;
14 | import de.retest.recheck.RecheckImpl;
15 | import de.retest.recheck.junit.jupiter.RecheckExtension;
16 |
17 | @ExtendWith( RecheckExtension.class )
18 | class CenterIT {
19 | WebDriver driver;
20 | Recheck re;
21 |
22 | @BeforeEach
23 | void before() {
24 | re = new RecheckImpl();
25 | }
26 |
27 | @ParameterizedTest( name = "center-{1}" )
28 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
29 | void testCenter( final WebDriver driver, final String name ) throws Exception {
30 | this.driver = driver;
31 | driver.get( page( CENTER ) );
32 |
33 | Thread.sleep( 1000 );
34 |
35 | re.check( driver, "open" );
36 | }
37 |
38 | @AfterEach
39 | void tearDown() {
40 | driver.quit();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature
3 | about: Create a issue that suggests or improves a feature
4 | labels: enhancement
5 | ---
6 |
7 | *Before submission, please check that ...*
8 |
9 | - [ ] this is related specifically to recheck-web and [recheck](https://github.com/retest/recheck) itself.
10 | - [ ] the [documentation](https://docs.retest.de/) does not mention a similar solution (e.g. possible configurations).
11 | - [ ] there are no open or closed issues that are related to this idea.
12 | - [ ] this, to my best knowledge, will not break something else.
13 |
14 |
15 |
16 | ## Problem
17 |
18 |
19 |
20 | ### Solution
21 |
22 |
23 |
24 | ### Additional Context
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/AutocheckingNavigationWrapper.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import java.net.URL;
4 |
5 | import org.openqa.selenium.WebDriver.Navigation;
6 |
7 | public class AutocheckingNavigationWrapper implements Navigation {
8 |
9 | private final Navigation delegate;
10 | private final AutocheckingRecheckDriver autocheckingDriver;
11 |
12 | public AutocheckingNavigationWrapper( final Navigation delegate,
13 | final AutocheckingRecheckDriver autocheckingDriver ) {
14 | this.delegate = delegate;
15 | this.autocheckingDriver = autocheckingDriver;
16 | }
17 |
18 | @Override
19 | public void back() {
20 | delegate.back();
21 | autocheckingDriver.check( "navigate-back" );
22 | }
23 |
24 | @Override
25 | public void forward() {
26 | delegate.forward();
27 | autocheckingDriver.check( "navigate-backforward" );
28 | }
29 |
30 | @Override
31 | public void to( final String url ) {
32 | delegate.to( url );
33 | autocheckingDriver.check( "navigate-to" );
34 | }
35 |
36 | @Override
37 | public void to( final URL url ) {
38 | delegate.to( url );
39 | autocheckingDriver.check( "navigate-to" );
40 | }
41 |
42 | @Override
43 | public void refresh() {
44 | delegate.refresh();
45 | autocheckingDriver.check( "refresh" );
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/FramePageIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static de.retest.web.testutils.PageFactory.page;
4 | import static de.retest.web.testutils.PageFactory.Page.FRAME_PAGE;
5 |
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.Recheck;
14 | import de.retest.recheck.RecheckImpl;
15 | import de.retest.recheck.junit.jupiter.RecheckExtension;
16 |
17 | @ExtendWith( RecheckExtension.class )
18 | class FramePageIT {
19 |
20 | WebDriver driver;
21 | Recheck re;
22 |
23 | @BeforeEach
24 | void setUp() {
25 | re = new RecheckImpl();
26 | }
27 |
28 | @ParameterizedTest( name = "frame-page-{1}" )
29 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
30 | void frame_page_html_should_be_checked( final WebDriver driver, final String name ) throws Exception {
31 | this.driver = driver;
32 | driver.get( page( FRAME_PAGE ) );
33 |
34 | Thread.sleep( 1000 );
35 | re.check( driver, "open" );
36 | }
37 |
38 | @AfterEach
39 | void tearDown() {
40 | driver.quit();
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SimplePageIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static de.retest.web.testutils.PageFactory.page;
4 | import static de.retest.web.testutils.PageFactory.Page.SIMPLE_PAGE;
5 |
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.Recheck;
14 | import de.retest.recheck.RecheckImpl;
15 | import de.retest.recheck.junit.jupiter.RecheckExtension;
16 |
17 | @ExtendWith( RecheckExtension.class )
18 | class SimplePageIT {
19 |
20 | WebDriver driver;
21 | Recheck re;
22 |
23 | @BeforeEach
24 | void setUp() {
25 | re = new RecheckImpl();
26 | }
27 |
28 | @ParameterizedTest( name = "simple-page-{1}" )
29 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
30 | void simple_html_page_should_be_checked( final WebDriver driver, final String name ) throws Exception {
31 | this.driver = driver;
32 | driver.get( page( SIMPLE_PAGE ) );
33 |
34 | Thread.sleep( 1000 );
35 |
36 | re.check( driver, "open" );
37 | }
38 |
39 | @AfterEach
40 | void tearDown() {
41 | driver.quit();
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/ShowcaseIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static de.retest.web.testutils.PageFactory.page;
4 | import static de.retest.web.testutils.PageFactory.Page.SHOWCASE;
5 |
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.Recheck;
14 | import de.retest.recheck.RecheckImpl;
15 | import de.retest.recheck.junit.jupiter.RecheckExtension;
16 |
17 | // Use extension to administer lifecycle of test
18 | @ExtendWith( RecheckExtension.class )
19 | class ShowcaseIT {
20 |
21 | WebDriver driver;
22 | Recheck re;
23 |
24 | @BeforeEach
25 | void setUp() {
26 | re = new RecheckImpl();
27 | }
28 |
29 | @ParameterizedTest( name = "showcase-{1}" )
30 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
31 | void showcase_html_should_be_checked( final WebDriver driver, final String name ) throws Exception {
32 | this.driver = driver;
33 | driver.get( page( SHOWCASE ) );
34 |
35 | Thread.sleep( 1000 );
36 |
37 | re.check( driver, "open" );
38 | }
39 |
40 | @AfterEach
41 | void tearDown() {
42 | driver.quit();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-release.yml:
--------------------------------------------------------------------------------
1 | name: Deploy releases
2 |
3 | on:
4 | create:
5 | tags: v*
6 |
7 | env:
8 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
9 |
10 | jobs:
11 | default:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 |
18 | - uses: actions/setup-java@v1
19 | with:
20 | java-version: 8
21 | server-id: ossrh
22 | server-username: MAVEN_USERNAME
23 | server-password: MAVEN_PASSWORD
24 |
25 | - name: Cache local Maven repository
26 | uses: actions/cache@v2
27 | with:
28 | path: ~/.m2/repository
29 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
30 | restore-keys: ${{ runner.os }}-maven-
31 |
32 | - name: Install gpg secret key
33 | run: |
34 | gpg --batch --import <(echo -e "${{ secrets.OSSRH_GPG_SECRET_KEY }}")
35 | gpg --list-secret-keys --keyid-format LONG
36 |
37 | - id: publish-to-central
38 | name: Publish to Central Repository
39 | run: |
40 | mvn ${MVN_ARGS} -DskipTests -Psign clean deploy
41 | env:
42 | GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PASSPHRASE }}
43 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
44 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
45 |
--------------------------------------------------------------------------------
/.github/workflows/release-hotfix-start.yml:
--------------------------------------------------------------------------------
1 | name: Release hotfix start
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | approval:
7 | description: 'Do you really want to start a HOTFIX release from MAIN branch?'
8 | required: true
9 | default: 'NO'
10 |
11 | env:
12 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
13 |
14 | jobs:
15 | default:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Verify approval
21 | run: "[[ $(echo ${{ github.event.inputs.approval }} | tr 'a-z' 'A-Z') == 'YES' ]]"
22 |
23 | - uses: actions/checkout@v2
24 | with:
25 | token: ${{ secrets.TRIGGER_ACTIONS_GITHUB_TOKEN }}
26 |
27 | - uses: actions/setup-java@v1
28 | with:
29 | java-version: 8
30 |
31 | - name: Cache local Maven repository
32 | uses: actions/cache@v2
33 | with:
34 | path: ~/.m2/repository
35 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
36 | restore-keys: ${{ runner.os }}-maven-
37 |
38 | - name: Configure Git user
39 | run: |
40 | git config user.email "ops+githubactions@retest.de"
41 | git config user.name "retest release github action"
42 |
43 |
44 | - name: Start release branch
45 | run: mvn ${MVN_ARGS} gitflow:hotfix-start
46 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/UnbreakableDriverTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.mock;
5 | import static org.mockito.Mockito.when;
6 | import static org.mockito.Mockito.withSettings;
7 |
8 | import org.junit.jupiter.api.Test;
9 | import org.openqa.selenium.WrapsDriver;
10 | import org.openqa.selenium.remote.RemoteWebDriver;
11 |
12 | class UnbreakableDriverTest {
13 |
14 | @Test
15 | void should_unwrap_wrapped_driver_if_possible() throws Exception {
16 | final RemoteWebDriver inner = mock( RemoteWebDriver.class );
17 |
18 | final RemoteWebDriver oldOuter = mock( RemoteWebDriver.class,
19 | withSettings().extraInterfaces( org.openqa.selenium.WrapsDriver.class ) );
20 | when( ((WrapsDriver) oldOuter).getWrappedDriver() ).thenReturn( inner );
21 |
22 | final RemoteWebDriver newOuter =
23 | mock( RemoteWebDriver.class, withSettings().extraInterfaces( WrapsDriver.class ) );
24 | when( ((WrapsDriver) newOuter).getWrappedDriver() ).thenReturn( inner );
25 |
26 | assertThat( new UnbreakableDriver( inner ).getWrappedDriver() ).isSameAs( inner );
27 | assertThat( new UnbreakableDriver( oldOuter ).getWrappedDriver() ).isSameAs( inner );
28 | assertThat( new UnbreakableDriver( newOuter ).getWrappedDriver() ).isSameAs( inner );
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/release-feature-start.yml:
--------------------------------------------------------------------------------
1 | name: Release feature start
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | approval:
7 | description: 'Do you really want to start a FEATURE release from DEVELOP branch?'
8 | required: true
9 | default: 'NO'
10 |
11 | env:
12 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
13 |
14 | jobs:
15 | default:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Verify approval
21 | run: "[[ $(echo ${{ github.event.inputs.approval }} | tr 'a-z' 'A-Z') == 'YES' ]]"
22 |
23 | - uses: actions/checkout@v2
24 | with:
25 | token: ${{ secrets.TRIGGER_ACTIONS_GITHUB_TOKEN }}
26 |
27 | - uses: actions/setup-java@v1
28 | with:
29 | java-version: 8
30 |
31 | - name: Cache local Maven repository
32 | uses: actions/cache@v2
33 | with:
34 | path: ~/.m2/repository
35 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
36 | restore-keys: ${{ runner.os }}-maven-
37 |
38 | - name: Configure Git user
39 | run: |
40 | git config user.email "ops+githubactions@retest.de"
41 | git config user.name "retest release github action"
42 |
43 |
44 | - name: Start release branch
45 | run: mvn ${MVN_ARGS} gitflow:release-start
46 |
--------------------------------------------------------------------------------
/.github/workflows/release-hotfix-finish.yml:
--------------------------------------------------------------------------------
1 | name: Release hotfix finish
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | approval:
7 | description: 'Do you really want to finish the current HOTFIX release?'
8 | required: true
9 | default: 'NO'
10 |
11 | env:
12 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
13 |
14 | jobs:
15 | default:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Verify approval
21 | run: "[[ $(echo ${{ github.event.inputs.approval }} | tr 'a-z' 'A-Z') == 'YES' ]]"
22 |
23 | - uses: actions/checkout@v2
24 | with:
25 | fetch-depth: 0
26 | token: ${{ secrets.TRIGGER_ACTIONS_GITHUB_TOKEN }}
27 |
28 | - uses: actions/setup-java@v1
29 | with:
30 | java-version: 8
31 |
32 | - name: Cache local Maven repository
33 | uses: actions/cache@v2
34 | with:
35 | path: ~/.m2/repository
36 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
37 | restore-keys: ${{ runner.os }}-maven-
38 |
39 | - name: Configure Git user
40 | run: |
41 | git config user.email "ops+githubactions@retest.de"
42 | git config user.name "retest release github action"
43 |
44 | - name: Tag and finish release branch
45 | run: mvn ${MVN_ARGS} gitflow:hotfix-finish
46 |
--------------------------------------------------------------------------------
/.github/workflows/release-feature-finish.yml:
--------------------------------------------------------------------------------
1 | name: Release feature finish
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | approval:
7 | description: 'Do you really want to finish the current FEATURE release?'
8 | required: true
9 | default: 'NO'
10 |
11 | env:
12 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
13 |
14 | jobs:
15 | default:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Verify approval
21 | run: "[[ $(echo ${{ github.event.inputs.approval }} | tr 'a-z' 'A-Z') == 'YES' ]]"
22 |
23 | - uses: actions/checkout@v2
24 | with:
25 | fetch-depth: 0
26 | token: ${{ secrets.TRIGGER_ACTIONS_GITHUB_TOKEN }}
27 |
28 | - uses: actions/setup-java@v1
29 | with:
30 | java-version: 8
31 |
32 | - name: Cache local Maven repository
33 | uses: actions/cache@v2
34 | with:
35 | path: ~/.m2/repository
36 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
37 | restore-keys: ${{ runner.os }}-maven-
38 |
39 | - name: Configure Git user
40 | run: |
41 | git config user.email "ops+githubactions@retest.de"
42 | git config user.name "retest release github action"
43 |
44 | - name: Tag and finish release branch
45 | run: mvn ${MVN_ARGS} gitflow:release-finish
46 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/AutocheckingCheckNamingStrategy.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import org.openqa.selenium.WebElement;
4 |
5 | /**
6 | * Interface to create names for checks for the {@link AutocheckingRecheckDriver}. These names must be unique within a
7 | * test, but can repeat in between tests (i.e. after calling {@link #nextTest()}).
8 | */
9 | public interface AutocheckingCheckNamingStrategy {
10 |
11 | /**
12 | * Return a string that is a unique identifier in the current test.
13 | *
14 | * @param action
15 | * The action to use when creating a semantic name (e.g. "click").
16 | * @param target
17 | * The target of the action or null.
18 | * @param params
19 | * Optional params of the action, like the URL for a "get" or the text for a "enter".
20 | * @return The unique identifier.
21 | */
22 | String getUniqueCheckName( String action, WebElement target, Object... params );
23 |
24 | /**
25 | * Return a string that is a unique identifier in the current test.
26 | *
27 | * @param action
28 | * The action to use when creating a semantic name (e.g. "navigate-back").
29 | * @return The unique identifier.
30 | */
31 | String getUniqueCheckName( String action );
32 |
33 | /**
34 | * Reset this naming strategy, such that strings can be reused in different tests.
35 | */
36 | void nextTest();
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/FormPageIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static de.retest.web.testutils.PageFactory.page;
4 | import static de.retest.web.testutils.PageFactory.Page.FORM_PAGE;
5 |
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.Recheck;
14 | import de.retest.recheck.RecheckImpl;
15 | import de.retest.recheck.junit.jupiter.RecheckExtension;
16 | import de.retest.web.selenium.By;
17 |
18 | @ExtendWith( RecheckExtension.class )
19 | class FormPageIT {
20 |
21 | WebDriver driver;
22 | Recheck re;
23 |
24 | @BeforeEach
25 | void setUp() {
26 | re = new RecheckImpl();
27 | }
28 |
29 | @ParameterizedTest( name = "form-page-{1}" )
30 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
31 | void form_page_html_should_be_checked( final WebDriver driver, final String name ) throws Exception {
32 | this.driver = driver;
33 | driver.get( page( FORM_PAGE ) );
34 |
35 | driver.findElement( By.id( "email" ) ).sendKeys( "Max" );
36 |
37 | Thread.sleep( 1000 );
38 | re.check( driver, "open" );
39 | }
40 |
41 | @AfterEach
42 | void tearDown() {
43 | driver.quit();
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/AttributesUtil.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | public class AttributesUtil {
8 |
9 | public static final String ABSOLUTE_X = "absolute-x";
10 | public static final String ABSOLUTE_Y = "absolute-y";
11 | public static final String ABSOLUTE_WIDTH = "absolute-width";
12 | public static final String ABSOLUTE_HEIGHT = "absolute-height";
13 |
14 | public static final String X = "x";
15 | public static final String Y = "y";
16 | public static final String WIDTH = "width";
17 | public static final String HEIGHT = "height";
18 |
19 | public static final String TEXT = "text";
20 |
21 | public static final String CLASS = "class";
22 | public static final String ID = "id";
23 | public static final String NAME = "name";
24 |
25 | // Mapped to our "type" attribute in WebElementPeer to avoid conflicts with HTML "type" attribute.
26 | public static final String TAG_NAME = "tagName";
27 |
28 | // Keys used in getAllElementsByPath.js.
29 | private static final Set identifyingAttributes = new HashSet<>( Arrays.asList( ABSOLUTE_X, ABSOLUTE_Y,
30 | ABSOLUTE_WIDTH, ABSOLUTE_HEIGHT, X, Y, WIDTH, HEIGHT, TEXT, CLASS, ID, NAME, TAG_NAME ) );
31 |
32 | private AttributesUtil() {}
33 |
34 | public static boolean isIdentifyingAttribute( final String key ) {
35 | return identifyingAttributes.contains( key );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SpecialContainerIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import org.junit.jupiter.api.AfterEach;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 | import org.openqa.selenium.WebDriver;
9 |
10 | import de.retest.recheck.Recheck;
11 | import de.retest.recheck.RecheckImpl;
12 | import de.retest.recheck.junit.jupiter.RecheckExtension;
13 | import de.retest.web.testutils.PageFactory;
14 |
15 | @ExtendWith( RecheckExtension.class )
16 | class SpecialContainerIT {
17 | WebDriver driver;
18 | Recheck re;
19 |
20 | @BeforeEach
21 | void setUp() {
22 | re = new RecheckImpl();
23 | }
24 |
25 | @ParameterizedTest( name = "special-container-{1}" )
26 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
27 | void testCenter( final WebDriver driver, final String name ) throws Exception {
28 | this.driver = driver;
29 | driver.get( PageFactory.toPageUrlString( "special-container.html" ) );
30 |
31 | re.check( driver, "open" );
32 |
33 | // Comment in to create golden master with the expected change
34 | // driver.findElement( org.openqa.selenium.By.id( "change-for-div" ) ).click();
35 |
36 | // This test should then have exactly one difference
37 | re.check( driver, "click" );
38 | }
39 |
40 | @AfterEach
41 | void tearDown() {
42 | driver.quit();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/driver/capabilities/PlatformMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.Objects;
6 |
7 | import org.openqa.selenium.Capabilities;
8 | import org.openqa.selenium.Platform;
9 | import org.openqa.selenium.WebDriverException;
10 |
11 | import de.retest.recheck.meta.MetadataProvider;
12 | import de.retest.web.meta.SeleniumMetadata;
13 | import lombok.AccessLevel;
14 | import lombok.RequiredArgsConstructor;
15 |
16 | @RequiredArgsConstructor( access = AccessLevel.PACKAGE )
17 | public final class PlatformMetadataProvider implements MetadataProvider {
18 |
19 | private final Capabilities capabilities;
20 |
21 | @Override
22 | public Map retrieve() {
23 | final Map map = new HashMap<>();
24 |
25 | final Platform platform = retrievePlatformSilently();
26 | if ( platform != null ) {
27 | map.put( SeleniumMetadata.OS_NAME, platform.toString() );
28 | final Object version = capabilities.getCapability( "platformVersion" );
29 | map.put( SeleniumMetadata.OS_VERSION, Objects.toString( version, "" ) );
30 | map.put( SeleniumMetadata.OS_ARCH, "" );
31 | }
32 |
33 | return map;
34 | }
35 |
36 | private Platform retrievePlatformSilently() {
37 | try {
38 | return capabilities.getPlatform();
39 | } catch ( final IllegalArgumentException | WebDriverException e ) {
40 | // failed
41 | return null;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/element/WebElementMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.element;
2 |
3 | import org.openqa.selenium.WebDriver;
4 | import org.openqa.selenium.WebElement;
5 | import org.openqa.selenium.remote.RemoteWebElement;
6 |
7 | import de.retest.recheck.meta.MetadataProvider;
8 | import de.retest.web.meta.driver.WebDriverMetadataProvider;
9 | import de.retest.web.util.SeleniumWrapperUtil;
10 | import de.retest.web.util.SeleniumWrapperUtil.WrapperOf;
11 | import lombok.extern.slf4j.Slf4j;
12 |
13 | @Slf4j
14 | public final class WebElementMetadataProvider {
15 |
16 | public static MetadataProvider of( final WebElement element ) {
17 | return extractDriverMetadataProvider( element );
18 | }
19 |
20 | private static MetadataProvider extractDriverMetadataProvider( final WebElement element ) {
21 | if ( SeleniumWrapperUtil.isWrapper( WrapperOf.DRIVER, element ) ) {
22 | final Object wrapped = SeleniumWrapperUtil.getWrapped( WrapperOf.DRIVER, element );
23 | return WebDriverMetadataProvider.of( (WebDriver) wrapped );
24 | }
25 | if ( SeleniumWrapperUtil.isWrapper( WrapperOf.ELEMENT, element ) ) {
26 | final Object wrapped = SeleniumWrapperUtil.getWrapped( WrapperOf.ELEMENT, element );
27 | return extractDriverMetadataProvider( (WebElement) wrapped );
28 | }
29 | log.debug( "Cannot retrieve driver from element {}. Element must be of '{}'. Returning empty metadata.",
30 | element, RemoteWebElement.class );
31 | return MetadataProvider.empty();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/driver/WebDriverMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import org.openqa.selenium.Dimension;
7 | import org.openqa.selenium.WebDriver;
8 |
9 | import de.retest.recheck.meta.MetadataProvider;
10 | import de.retest.recheck.meta.MultiMetadataProvider;
11 | import de.retest.web.meta.SeleniumMetadata;
12 | import de.retest.web.meta.driver.capabilities.CapabilityMetadataProvider;
13 | import lombok.AccessLevel;
14 | import lombok.RequiredArgsConstructor;
15 |
16 | @RequiredArgsConstructor( access = AccessLevel.PACKAGE )
17 | public final class WebDriverMetadataProvider implements MetadataProvider {
18 |
19 | private final WebDriver driver;
20 |
21 | public static MetadataProvider of( final WebDriver driver ) {
22 | return MultiMetadataProvider.of( //
23 | CapabilityMetadataProvider.of( driver ), //
24 | new WebDriverMetadataProvider( driver ) //
25 | );
26 | }
27 |
28 | @Override
29 | public Map retrieve() {
30 | final Map map = new HashMap<>();
31 |
32 | map.put( SeleniumMetadata.DRIVER_TYPE, driver.getClass().getSimpleName() );
33 | map.put( SeleniumMetadata.URL, driver.getCurrentUrl() );
34 |
35 | final Dimension size = driver.manage().window().getSize();
36 | map.put( SeleniumMetadata.WINDOW_WIDTH, String.valueOf( size.getWidth() ) );
37 | map.put( SeleniumMetadata.WINDOW_HEIGHT, String.valueOf( size.getHeight() ) );
38 |
39 | return map;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/init.js:
--------------------------------------------------------------------------------
1 | /*-----------------------------------------------------------------------------------
2 | /*
3 | /* Init JS
4 | /*
5 | -----------------------------------------------------------------------------------*/
6 |
7 | jQuery(document).ready(function() {
8 |
9 | /*----------------------------------------------------*/
10 | /* Navigation - Double Tap to Go
11 | ------------------------------------------------------*/
12 |
13 | $( '#nav li:has(ul)' ).doubleTapToGo();
14 |
15 | /*----------------------------------------------------*/
16 | /* Back To Top Button
17 | /*----------------------------------------------------*/
18 | var pxShow = 300; //height on which the button will show
19 | var fadeInTime = 400; //how slow/fast you want the button to show
20 | var fadeOutTime = 400; //how slow/fast you want the button to hide
21 | var scrollSpeed = 300; //how slow/fast you want the button to scroll to top. can be a value, 'slow', 'normal' or 'fast'
22 |
23 | // Show or hide the sticky footer button
24 | jQuery(window).scroll(function() {
25 |
26 | if (jQuery(window).scrollTop() >= pxShow) {
27 | jQuery("#go-top").fadeIn(fadeInTime);
28 | } else {
29 | jQuery("#go-top").fadeOut(fadeOutTime);
30 | }
31 |
32 | });
33 |
34 | // Animate the scroll to top
35 | jQuery("#go-top a").click(function() {
36 | jQuery("html, body").animate({scrollTop:0}, scrollSpeed);
37 | return false;
38 | });
39 | });
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/screenshot/ScreenshotProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import java.awt.image.BufferedImage;
4 |
5 | import org.openqa.selenium.WebDriver;
6 | import org.openqa.selenium.WebElement;
7 |
8 | /**
9 | * The screenshot provider for way screenshots are taken.
10 | *
11 | * The ScreenshotProvider can choose whether screenshots are taken for the full page, just the viewport or none at all.
12 | * The Default implementation is {@link ViewportOnlyMinimalScreenshot}.
13 | */
14 | public interface ScreenshotProvider {
15 |
16 | /**
17 | * Takes the screenshot from the {@link WebDriver}.
18 | *
19 | * @param driver
20 | * Webdriver for taking the screenshot.
21 | * @return screenshot image of the tested page. May return {@code null} if screenshots are not supported.
22 | * @throws Exception
23 | * If an exception occurred during screenshot creation.
24 | */
25 | BufferedImage shoot( WebDriver driver ) throws Exception;
26 |
27 | /**
28 | * Takes the screenshot from {@link WebElement}.
29 | *
30 | * @param driver
31 | * Webdriver for taking the screenshot.
32 | * @param element
33 | * WebElement where screenshot is taken from.
34 | * @return screenshot image of tested web element. May return {@code null} if screenshots are not supported.
35 | * @throws Exception
36 | * If an exception occurred during screenshot creation.
37 | */
38 | BufferedImage shoot( WebDriver driver, WebElement element ) throws Exception;
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/testutils/SystemPropertyExtension.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.testutils;
2 |
3 | import java.util.Properties;
4 |
5 | import org.junit.jupiter.api.extension.AfterEachCallback;
6 | import org.junit.jupiter.api.extension.BeforeEachCallback;
7 | import org.junit.jupiter.api.extension.ExtensionContext;
8 | import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
9 | import org.junit.jupiter.api.extension.ExtensionContext.Store;
10 |
11 | public class SystemPropertyExtension implements BeforeEachCallback, AfterEachCallback {
12 |
13 | private static final String BACKUP_STORE_KEY = "backup";
14 |
15 | @Override
16 | public void beforeEach( final ExtensionContext context ) throws Exception {
17 | final Properties backup = new Properties();
18 | backup.putAll( System.getProperties() );
19 | final Store store = context.getStore( Namespace.create( getClass(), context.getRequiredTestMethod() ) );
20 | store.put( BACKUP_STORE_KEY, backup );
21 | final SystemProperty prop = context.getTestMethod().get().getAnnotation( SystemProperty.class );
22 | if ( prop.value().isEmpty() ) {
23 | System.clearProperty( prop.key() );
24 | } else {
25 | System.setProperty( prop.key(), prop.value() );
26 | }
27 | }
28 |
29 | @Override
30 | public void afterEach( final ExtensionContext context ) throws Exception {
31 | final Store store = context.getStore( Namespace.create( getClass(), context.getRequiredTestMethod() ) );
32 | final Properties backup = store.get( BACKUP_STORE_KEY, Properties.class );
33 | System.setProperties( backup );
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/screenshot/FullPageScreenshot.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import static de.retest.recheck.ui.image.ImageUtils.resizeImage;
4 | import static de.retest.web.screenshot.ScreenshotProviders.SCALE;
5 |
6 | import java.awt.image.BufferedImage;
7 |
8 | import org.openqa.selenium.WebDriver;
9 | import org.openqa.selenium.WebElement;
10 | import org.openqa.selenium.chrome.ChromeDriver;
11 |
12 | import com.assertthat.selenium_shutterbug.core.Capture;
13 | import com.assertthat.selenium_shutterbug.core.Shutterbug;
14 |
15 | /**
16 | * Scrolls to the bottom of the page and takes a screenshot of the entire page.
17 | */
18 | public class FullPageScreenshot implements ScreenshotProvider {
19 |
20 | private static final int SCROLL_TIMEOUT_MS = 100;
21 | private static final boolean USE_DEVICE_PIXEL_RATIO = true;
22 |
23 | @Override
24 | public BufferedImage shoot( final WebDriver driver ) {
25 | if ( driver instanceof ChromeDriver ) {
26 | final BufferedImage image = Shutterbug //
27 | .shootPage( driver, Capture.FULL, SCROLL_TIMEOUT_MS, USE_DEVICE_PIXEL_RATIO ) //
28 | .getImage();
29 | return resizeImage( image, image.getWidth() / SCALE, image.getHeight() / SCALE );
30 | }
31 | return Shutterbug //
32 | .shootPage( driver, Capture.FULL, SCROLL_TIMEOUT_MS, USE_DEVICE_PIXEL_RATIO ) //
33 | .getImage();
34 | }
35 |
36 | @Override
37 | public BufferedImage shoot( final WebDriver driver, final WebElement element ) {
38 | return Shutterbug.shootElement( driver, element, USE_DEVICE_PIXEL_RATIO ).getImage();
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug
3 | about: Create a bug report to help us improve
4 | labels: bug
5 | ---
6 |
7 | *Before submission, please check that ...*
8 |
9 | - [ ] this is related specifically to recheck-web and [recheck](https://github.com/retest/recheck) itself.
10 | - [ ] the [documentation](https://docs.retest.de/) does not mention a fix (e.g. wrong API usage, ...).
11 | - [ ] the bug is not related to a customization (e.g. custom implementations).
12 | - [ ] there are no open or closed issues that are related to this problem.
13 |
14 |
15 |
16 | ## Describe the Bug
17 |
18 |
19 |
20 | ### How to Reproduce?
21 |
22 |
23 |
24 | Steps to reproduce the behavior:
25 | 1. ...
26 | 2. ...
27 | 3. ...
28 |
29 | ### Setup
30 |
31 |
32 |
33 | - recheck-web: [e.g. 1.6.0]
34 | - Selenium version: [e.g. 3.141.59]
35 | - Testing framework: [e.g. JUnit5 5.5.2, TestNG 7.0.0]
36 | - OS: [e.g. MacOS 14, Windows 10]
37 | - Java version: [e.g. OpenJDK 11]
38 |
39 | ### Additional Context
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/css/RegexTransformer.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium.css;
2 |
3 | import java.util.function.Predicate;
4 | import java.util.regex.Matcher;
5 | import java.util.regex.Pattern;
6 |
7 | import de.retest.recheck.ui.descriptors.Element;
8 |
9 | class RegexTransformer implements Transformer {
10 |
11 | private static final String REMAINING_GROUP = "remaining";
12 | private static final String REMAINING = "(?<" + REMAINING_GROUP + ">.*)$";
13 | private static final String START_OF_LINE = "^";
14 |
15 | private final Pattern cssPattern;
16 | private final PredicateFactory factory;
17 |
18 | private RegexTransformer( final String pattern, final PredicateFactory factory ) {
19 | cssPattern = Pattern.compile( START_OF_LINE + pattern + REMAINING );
20 | this.factory = factory;
21 | }
22 |
23 | static Transformer of( final String pattern, final PredicateFactory factory ) {
24 | return new RegexTransformer( pattern, factory );
25 | }
26 |
27 | @Override
28 | public Selector transform( final String selector ) {
29 | final Matcher matcher = cssPattern.matcher( selector );
30 | if ( matcher.find() ) {
31 | return newSelector( matcher );
32 | }
33 | return Selector.unsupported( selector );
34 | }
35 |
36 | private Selector newSelector( final Matcher matcher ) {
37 | final String cssAttribute = matcher.group( 1 );
38 | final String remainingSelector = matcher.group( REMAINING_GROUP ).trim();
39 | final Predicate predicate = factory.create( cssAttribute );
40 | return Selector.supported( remainingSelector, predicate );
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/driver/WebDriverMetadataProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.RETURNS_MOCKS;
5 | import static org.mockito.Mockito.mock;
6 | import static org.mockito.Mockito.when;
7 |
8 | import org.apache.commons.lang3.tuple.Pair;
9 | import org.junit.jupiter.api.Test;
10 | import org.openqa.selenium.WebDriver;
11 | import org.openqa.selenium.remote.RemoteWebDriver;
12 |
13 | import de.retest.recheck.meta.MetadataProvider;
14 | import de.retest.web.meta.SeleniumMetadata;
15 | import de.retest.web.selenium.UnbreakableDriver;
16 |
17 | class WebDriverMetadataProviderTest {
18 |
19 | @Test
20 | void retrieve_should_directly_take_driver_type() throws Exception {
21 | final WebDriver driver = mock( WebDriver.class, RETURNS_MOCKS );
22 |
23 | final MetadataProvider cut = WebDriverMetadataProvider.of( driver );
24 |
25 | assertThat( cut.retrieve() )
26 | .contains( Pair.of( SeleniumMetadata.DRIVER_TYPE, driver.getClass().getSimpleName() ) );
27 | }
28 |
29 | @Test
30 | void retrieve_should_directly_take_driver_type_if_wrapped() throws Exception {
31 | final UnbreakableDriver driver = mock( UnbreakableDriver.class, RETURNS_MOCKS );
32 | when( driver.getWrappedDriver() ).thenReturn( mock( RemoteWebDriver.class, RETURNS_MOCKS ) );
33 |
34 | final MetadataProvider cut = WebDriverMetadataProvider.of( driver );
35 |
36 | assertThat( cut.retrieve() )
37 | .contains( Pair.of( SeleniumMetadata.DRIVER_TYPE, driver.getClass().getSimpleName() ) );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/WikipediaIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import org.junit.jupiter.api.AfterEach;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.Disabled;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.api.extension.ExtendWith;
8 | import org.openqa.selenium.WebDriver;
9 |
10 | import de.retest.recheck.Recheck;
11 | import de.retest.recheck.RecheckImpl;
12 | import de.retest.recheck.RecheckOptions;
13 | import de.retest.recheck.junit.jupiter.RecheckExtension;
14 | import de.retest.web.RecheckWebOptions;
15 | import de.retest.web.testutils.PageFactory;
16 | import de.retest.web.testutils.WebDriverFactory;
17 | import de.retest.web.testutils.WebDriverFactory.Driver;
18 |
19 | @Disabled( "We only use this to create an example.report file for review." )
20 | @ExtendWith( RecheckExtension.class )
21 | class WikipediaIT {
22 |
23 | WebDriver driver;
24 | Recheck re;
25 |
26 | @BeforeEach
27 | void setUp() throws Exception {
28 | driver = WebDriverFactory.driver( Driver.CHROME );
29 | final RecheckOptions opts = RecheckWebOptions.builder() //
30 | .enableScreenshots() //
31 | .build();
32 | re = new RecheckImpl( opts );
33 | }
34 |
35 | @Test
36 | void myWikipediaTest() throws Exception {
37 | // Switch to "expected" subfolder to restore original page.
38 | driver.get( PageFactory.toPageUrlString( "wikipedia/actual/wikipedia-characterization-test.html" ) );
39 | re.check( driver, "characterization-testing-page" );
40 | }
41 |
42 | @AfterEach
43 | void tearDown() throws Exception {
44 | driver.close();
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/testutils/PageFactory.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.testutils;
2 |
3 | import java.net.MalformedURLException;
4 | import java.nio.file.Paths;
5 | import java.util.Arrays;
6 | import java.util.stream.Stream;
7 |
8 | public class PageFactory {
9 |
10 | private static final String BASE_PATH = "src/test/resources/pages/";
11 |
12 | public enum Page {
13 | SHOWCASE,
14 | SIMPLE_PAGE,
15 | FORM_PAGE,
16 | FRAME_PAGE,
17 | CENTER,
18 | COVERED_PAGE
19 | }
20 |
21 | public static String page( final Page page ) {
22 | switch ( page ) {
23 | case SHOWCASE: {
24 | return toPageUrlString( "showcase/retest.html" );
25 | }
26 | case SIMPLE_PAGE: {
27 | return toPageUrlString( "simple-page.html" );
28 | }
29 | case FORM_PAGE: {
30 | return toPageUrlString( "form-page.html" );
31 | }
32 | case CENTER: {
33 | return toPageUrlString( "centered.html" );
34 | }
35 | case FRAME_PAGE: {
36 | return toPageUrlString( "frame-outer.html" );
37 | }
38 | case COVERED_PAGE: {
39 | return toPageUrlString( "covered-page.html" );
40 | }
41 | default:
42 | throw new IllegalArgumentException( "No \"" + page + "\" page available." );
43 | }
44 | }
45 |
46 | public static final String toPageUrlString( final String relativePath ) {
47 | try {
48 | return Paths.get( BASE_PATH, relativePath ).toUri().toURL().toString();
49 | } catch ( final MalformedURLException e ) {
50 | return null;
51 | }
52 | }
53 |
54 | public static Stream pages() {
55 | return Arrays.stream( Page.values() ).map( PageFactory::page );
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/driver/capabilities/CapabilityMetadataProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.RETURNS_MOCKS;
5 | import static org.mockito.Mockito.mock;
6 | import static org.mockito.Mockito.when;
7 |
8 | import org.junit.jupiter.api.Test;
9 | import org.openqa.selenium.WebDriver;
10 | import org.openqa.selenium.remote.RemoteWebDriver;
11 |
12 | import de.retest.recheck.meta.MetadataProvider;
13 | import de.retest.web.selenium.UnbreakableDriver;
14 |
15 | class CapabilityMetadataProviderTest {
16 |
17 | @Test
18 | void of_should_identify_driver() throws Exception {
19 | final WebDriver driver = mock( RemoteWebDriver.class, RETURNS_MOCKS );
20 |
21 | final MetadataProvider cut = CapabilityMetadataProvider.of( driver );
22 |
23 | assertThat( cut.retrieve() ).isNotEmpty();
24 | }
25 |
26 | @Test
27 | void of_should_identify_wrapped_driver() throws Exception {
28 | final UnbreakableDriver wrapsDriver = mock( UnbreakableDriver.class, RETURNS_MOCKS );
29 | when( wrapsDriver.getWrappedDriver() ).thenReturn( mock( RemoteWebDriver.class, RETURNS_MOCKS ) );
30 |
31 | final MetadataProvider cut = CapabilityMetadataProvider.of( wrapsDriver );
32 |
33 | assertThat( cut.retrieve() ).isNotEmpty();
34 | }
35 |
36 | @Test
37 | void of_should_not_use_plain_web_driver_and_return_empty_provider() throws Exception {
38 | final WebDriver driver = mock( WebDriver.class );
39 |
40 | final MetadataProvider cut = CapabilityMetadataProvider.of( driver );
41 |
42 | assertThat( cut.retrieve() ).isEmpty();
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/AutocheckingTargetLocator.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import org.openqa.selenium.Alert;
4 | import org.openqa.selenium.WebDriver;
5 | import org.openqa.selenium.WebElement;
6 |
7 | import lombok.RequiredArgsConstructor;
8 | import org.openqa.selenium.WindowType;
9 |
10 | @RequiredArgsConstructor
11 | public class AutocheckingTargetLocator implements WebDriver.TargetLocator {
12 |
13 | private final WebDriver.TargetLocator delegate;
14 | private final AutocheckingRecheckDriver driver;
15 |
16 | @Override
17 | public WebDriver frame( final int index ) {
18 | return delegate.frame( index );
19 | }
20 |
21 | @Override
22 | public WebDriver frame( final String nameOrId ) {
23 | return delegate.frame( nameOrId );
24 | }
25 |
26 | @Override
27 | public WebDriver frame( final WebElement frameElement ) {
28 | return delegate.frame( frameElement );
29 | }
30 |
31 | @Override
32 | public WebDriver parentFrame() {
33 | return delegate.parentFrame();
34 | }
35 |
36 | @Override
37 | public WebDriver window( final String nameOrHandle ) {
38 | final WebDriver driver = delegate.window( nameOrHandle );
39 | this.driver.check( "switch-window" );
40 | return driver;
41 | }
42 |
43 | @Override
44 | public WebDriver newWindow(WindowType typeHint) {
45 | return delegate.newWindow(typeHint);
46 | }
47 |
48 | @Override
49 | public WebDriver defaultContent() {
50 | return delegate.defaultContent();
51 | }
52 |
53 | @Override
54 | public WebElement activeElement() {
55 | return driver.wrap( delegate.activeElement() ) ;
56 | }
57 |
58 | @Override
59 | public Alert alert() {
60 | return delegate.alert();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/.mergify.yml:
--------------------------------------------------------------------------------
1 | queue_rules:
2 | - name: default
3 | conditions:
4 | - "#approved-reviews-by>=1"
5 | - "#changes-requested-reviews-by=0"
6 | - "-draft"
7 |
8 | pull_request_rules:
9 | - name: "rebase non-release PRs"
10 | conditions:
11 | - "head~=^(?!(release|hotfix)).*$"
12 | actions:
13 | rebase:
14 |
15 | - name: "merge non-release SonarCloud-checked PRs with strict rebase"
16 | conditions:
17 | - "head~=^(?!(release|hotfix)).*$"
18 | - "#approved-reviews-by>=1"
19 | - "#changes-requested-reviews-by=0"
20 | - "#commits-behind=0"
21 | - "-draft"
22 | - "check-success=SonarCloud Code Analysis"
23 | actions:
24 | queue:
25 | name: default
26 | update_method: rebase
27 |
28 | - name: "merge non-release without-SonarCloud-but-from-dependabot PRs with strict rebase"
29 | conditions:
30 | - "head~=^(?!(release|hotfix)).*$"
31 | - "#approved-reviews-by>=1"
32 | - "#changes-requested-reviews-by=0"
33 | - "#commits-behind=0"
34 | - "-draft"
35 | - "author=dependabot[bot]"
36 | actions:
37 | queue:
38 | name: default
39 | update_method: rebase
40 |
41 | - name: "merge release PRs with strict merge"
42 | conditions:
43 | - "head~=^(release|hotfix).*$"
44 | - "#approved-reviews-by>=1"
45 | - "#changes-requested-reviews-by=0"
46 | - "#commits-behind=0"
47 | - "-draft"
48 | actions:
49 | queue:
50 | name: default
51 | update_method: rebase
52 |
53 | - name: "delete PR branches after merge"
54 | conditions:
55 | - merged
56 | actions:
57 | delete_head_branch: {}
58 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/RecheckWebProperties.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import org.aeonbits.owner.Config.LoadPolicy;
4 | import org.aeonbits.owner.Config.LoadType;
5 | import org.aeonbits.owner.Config.Sources;
6 | import org.aeonbits.owner.ConfigCache;
7 | import org.aeonbits.owner.Reloadable;
8 |
9 | import de.retest.web.screenshot.ScreenshotProvider;
10 | import de.retest.web.screenshot.ScreenshotProviders.ScreenshotProviderConverter;
11 |
12 | /**
13 | * Interface for additional recheck-web properties, determined via system properties (first) or
14 | * .retest/retest.properties (second). For more information, please have a look at the
15 | * documentation .
16 | */
17 | @LoadPolicy( LoadType.MERGE )
18 | @Sources( { "system:properties", "file:${projectroot}/.retest/retest.properties" } )
19 | public interface RecheckWebProperties extends Reloadable {
20 |
21 | /*
22 | * Basic usage.
23 | */
24 |
25 | static RecheckWebProperties getInstance() {
26 | final RecheckWebProperties instance = ConfigCache.getOrCreate( RecheckWebProperties.class );
27 | instance.reload();
28 | return instance;
29 | }
30 |
31 | /*
32 | * Properties, their key constants and related functionality.
33 | */
34 |
35 | static final String SCREENSHOT_PROVIDER_PROPERTY_KEY = "de.retest.recheck.web.screenshot.provider";
36 |
37 | /**
38 | * @return The {@link ScreenshotProvider} to be used.
39 | */
40 | @Key( SCREENSHOT_PROVIDER_PROPERTY_KEY )
41 | @DefaultValue( "viewportOnlyMinimal" )
42 | @ConverterClass( ScreenshotProviderConverter.class )
43 | ScreenshotProvider screenshotProvider();
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/.retest/filter/recheck-pseudo-elements.filter:
--------------------------------------------------------------------------------
1 | # Ignore file for recheck. Please do not delete, even if it is empty.
2 |
3 | # You can ignore specific attributes like so:
4 | # matcher: retestid=div-b4f23, attribute: font.*
5 |
6 | # To ignore whole subtrees, use e.g. XPath:
7 | # matcher: xpath=HTML[1]/BODY[1]/DIV[1]/DIV[1]/DIV[1]
8 |
9 | # To ignore attributes globally, use:
10 | # attribute=class
11 |
12 | # More details and examples can be found here:
13 | # https://retest.github.io/docs/recheck/how-ignore-works-in-recheck/
14 |
15 | # Ignore non-visible attributes:
16 | attribute=class
17 | attribute=ping
18 | attribute-regex=data-.*
19 | attribute=jsdata
20 | attribute=jsaction
21 | attribute=cd_frame_id_
22 | attribute=font-family
23 | attribute=font-size
24 | attribute=margin-bottom
25 | attribute=margin-top
26 | attribute=display
27 | attribute=box-sizing
28 | attribute=background-size
29 | attribute=left
30 | attribute=right
31 | attribute=min-height
32 | attribute=min-width
33 | attribute-regex=border-.*-width
34 | attribute-regex=border-.*-style
35 | attribute-regex=padding-.*
36 | attribute=line-height
37 | attribute=quotes
38 | matcher: type=meta, attribute-regex=.*
39 |
40 | # Have the tests pass locally:
41 | attribute=outline
42 | attribute=outline-width
43 |
44 | # So we can have a different ID in SimpleAutocheckingDriverShowcaseIT:
45 | matcher: xpath=html[1]/body[1]/form[1]/input[1], attribute=id
46 |
47 | # Ignore pixel diffs up to 5 pixels:
48 | pixel-diff=5px
49 |
50 | # Ignore OS-specific differences
51 | matcher: retestid=firefox_cant, attribute=text
52 | matcher: retestid=to_fully_automa, attribute=covered
53 | matcher: retestid=wallace, attribute=covered
54 | matcher: type=option, attribute=covered
55 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/testutils/WebDriverFactory.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.testutils;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 | import java.util.stream.Stream;
6 |
7 | import org.junit.jupiter.params.provider.Arguments;
8 | import org.openqa.selenium.WebDriver;
9 | import org.openqa.selenium.chrome.ChromeDriver;
10 | import org.openqa.selenium.chrome.ChromeOptions;
11 | import org.openqa.selenium.firefox.FirefoxDriver;
12 | import org.openqa.selenium.firefox.FirefoxOptions;
13 | import org.openqa.selenium.remote.RemoteWebDriver;
14 |
15 | public class WebDriverFactory {
16 |
17 | public enum Driver {
18 | CHROME,
19 | FIREFOX
20 | }
21 |
22 | public static RemoteWebDriver driver( final Driver driver ) {
23 | switch ( driver ) {
24 | case CHROME: {
25 | return new ChromeDriver( new ChromeOptions().addArguments( commonArguments() ) );
26 | }
27 | case FIREFOX: {
28 | return new FirefoxDriver( new FirefoxOptions().addArguments( commonArguments() ) );
29 | }
30 | default:
31 | throw new IllegalArgumentException( "No \"" + driver + "\" driver available." );
32 | }
33 | }
34 |
35 | public static Stream drivers() {
36 | return Arrays.stream( Driver.values() ).map( WebDriverFactory::driver ).map( WebDriverFactory::toArguments );
37 | }
38 |
39 | private static Arguments toArguments( final WebDriver driver ) {
40 | return Arguments.of( driver, driver.getClass().getSimpleName() );
41 | }
42 |
43 | public static List commonArguments() {
44 | return Arrays.asList(
45 | // Enable headless mode for faster execution.
46 | "--headless",
47 | // Fix window size for stable results.
48 | "--window-size=1200,800" );
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/element/WebElementMetadataProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.element;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.RETURNS_MOCKS;
5 | import static org.mockito.Mockito.mock;
6 | import static org.mockito.Mockito.when;
7 |
8 | import org.junit.jupiter.api.Test;
9 | import org.openqa.selenium.WebElement;
10 | import org.openqa.selenium.remote.RemoteWebElement;
11 |
12 | import de.retest.recheck.meta.MetadataProvider;
13 | import de.retest.web.selenium.AutocheckingRecheckDriver;
14 | import de.retest.web.selenium.AutocheckingWebElement;
15 |
16 | class WebElementMetadataProviderTest {
17 |
18 | @Test
19 | void of_should_extract_driver_from_element() throws Exception {
20 | final RemoteWebElement element = mock( RemoteWebElement.class, RETURNS_MOCKS );
21 |
22 | final MetadataProvider cut = WebElementMetadataProvider.of( element );
23 |
24 | assertThat( cut.retrieve() ).isNotEmpty();
25 | }
26 |
27 | @Test
28 | void of_should_extract_driver_from_wrapped() throws Exception {
29 | final AutocheckingWebElement element = mock( AutocheckingWebElement.class );
30 | when( element.getWrappedDriver() ).thenReturn( mock( AutocheckingRecheckDriver.class, RETURNS_MOCKS ) );
31 |
32 | final MetadataProvider cut = WebElementMetadataProvider.of( element );
33 |
34 | assertThat( cut.retrieve() ).isNotEmpty();
35 | }
36 |
37 | @Test
38 | void of_should_not_use_plain_web_element_and_return_empty_provider() throws Exception {
39 | final WebElement element = mock( WebElement.class );
40 |
41 | final MetadataProvider cut = WebElementMetadataProvider.of( element );
42 |
43 | assertThat( cut.retrieve() ).isEmpty();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/jquery.waypoints.inview.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | Waypoints Inview Shortcut - 4.0.0
3 | Copyright © 2011-2015 Caleb Troughton
4 | Licensed under the MIT license.
5 | https://github.com/imakewebthings/waypoints/blog/master/licenses.txt
6 | */
7 | !function(){"use strict";function t(){}function e(t){this.options=i.Adapter.extend({},e.defaults,t),this.axis=this.options.horizontal?"horizontal":"vertical",this.waypoints=[],this.element=this.options.element,this.createWaypoints()}var i=window.Waypoint;e.prototype.createWaypoints=function(){for(var t={vertical:[{down:"enter",up:"exited",offset:"100%"},{down:"entered",up:"exit",offset:"bottom-in-view"},{down:"exit",up:"entered",offset:0},{down:"exited",up:"enter",offset:function(){return-this.adapter.outerHeight()}}],horizontal:[{right:"enter",left:"exited",offset:"100%"},{right:"entered",left:"exit",offset:"right-in-view"},{right:"exit",left:"entered",offset:0},{right:"exited",left:"enter",offset:function(){return-this.adapter.outerWidth()}}]},e=0,i=t[this.axis].length;i>e;e++){var n=t[this.axis][e];this.createWaypoint(n)}},e.prototype.createWaypoint=function(t){var e=this;this.waypoints.push(new i({context:this.options.context,element:this.options.element,enabled:this.options.enabled,handler:function(t){return function(i){e.options[t[i]].call(e,i)}}(t),offset:t.offset,horizontal:this.options.horizontal}))},e.prototype.destroy=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].destroy();this.waypoints=[]},e.prototype.disable=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].disable()},e.prototype.enable=function(){for(var t=0,e=this.waypoints.length;e>t;t++)this.waypoints[t].enable()},e.defaults={context:window,enabled:!0,enter:t,entered:t,exit:t,exited:t},i.Inview=e}();
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/WriteToResultWarningConsumer.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import java.util.Collection;
4 | import java.util.function.Consumer;
5 | import java.util.function.Predicate;
6 | import java.util.stream.Stream;
7 |
8 | import de.retest.recheck.ui.diff.AttributeDifference;
9 | import de.retest.recheck.ui.diff.ElementDifference;
10 | import de.retest.recheck.ui.diff.ElementIdentificationWarning;
11 | import lombok.RequiredArgsConstructor;
12 |
13 | @RequiredArgsConstructor
14 | public class WriteToResultWarningConsumer implements Consumer {
15 |
16 | private final ElementDifferenceRetriever retriever;
17 |
18 | @Override
19 | public void accept( final QualifiedElementWarning warning ) {
20 | retriever.getDifferences() //
21 | .filter( matchesRetestId( warning.getRetestId() ) ) //
22 | .map( ElementDifference::getAttributeDifferences ) //
23 | .flatMap( Collection::stream ) //
24 | .filter( matchesAttributeKey( warning.getAttributeKey() ) ) // Should only be one difference
25 | .forEach( addWarning( warning.getWarning() ) );
26 | }
27 |
28 | private Predicate matchesRetestId( final String retestId ) {
29 | return difference -> difference.getRetestId().equals( retestId );
30 | }
31 |
32 | private Predicate matchesAttributeKey( final String attributeKey ) {
33 | return difference -> difference.getKey().equals( attributeKey );
34 | }
35 |
36 | private Consumer addWarning( final ElementIdentificationWarning warning ) {
37 | return difference -> difference.addElementIdentificationWarning( warning );
38 | }
39 |
40 | @FunctionalInterface
41 | public interface ElementDifferenceRetriever {
42 |
43 | Stream getDifferences();
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.retest/recheck.ignore:
--------------------------------------------------------------------------------
1 | # Ignore file for recheck. Please do not delete, even if it is empty.
2 |
3 | import: volatile-metadata.filter
4 |
5 | # You can ignore specific attributes like so:
6 | # matcher: retestid=div-b4f23, attribute: font.*
7 |
8 | # To ignore whole subtrees, use e.g. XPath:
9 | # matcher: xpath=HTML[1]/BODY[1]/DIV[1]/DIV[1]/DIV[1]
10 |
11 | # To ignore attributes globally, use:
12 | # attribute=class
13 |
14 | # More details and examples can be found here:
15 | # https://retest.github.io/docs/recheck/how-ignore-works-in-recheck/
16 |
17 | # Ignore non-visible attributes:
18 | attribute=class
19 | attribute=ping
20 | attribute-regex=data-.*
21 | attribute=jsdata
22 | attribute=jsaction
23 | attribute=cd_frame_id_
24 | attribute=color
25 | attribute=font-family
26 | attribute=font-size
27 | attribute=margin-bottom
28 | attribute=margin-top
29 | attribute=display
30 | attribute=box-sizing
31 | attribute=background-size
32 | attribute=left
33 | attribute=right
34 | attribute=min-height
35 | attribute=min-width
36 | attribute-regex=border-.*-width
37 | attribute-regex=.*-color
38 | attribute-regex=border-.*-style
39 | attribute-regex=padding-.*
40 | attribute=line-height
41 | attribute=quotes
42 | attribute=outline-offset
43 | attribute=unicode-bidi
44 | matcher: type=meta, attribute-regex=.*
45 |
46 | # Have the tests pass locally:
47 | attribute=outline
48 | attribute=outline-width
49 |
50 | # So we can have a different ID in SimpleAutocheckingDriverShowcaseIT:
51 | matcher: xpath=html[1]/body[1]/form[1]/input[1], attribute=id
52 |
53 | # Ignore pixel diffs up to 5 pixels:
54 | pixel-diff=5px
55 |
56 | # Ignore OS-specific differences
57 | matcher: retestid=firefox_cant, attribute=text
58 | matcher: retestid=to_fully_automa, attribute=covered
59 | matcher: retestid=wallace, attribute=covered
60 | matcher: type=option, attribute=covered
61 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/screenshot/ViewportOnlyMinimalScreenshot.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import java.awt.image.BufferedImage;
4 |
5 | import org.openqa.selenium.WebDriver;
6 | import org.openqa.selenium.WebElement;
7 |
8 | import com.assertthat.selenium_shutterbug.core.Shutterbug;
9 |
10 | import de.retest.recheck.ui.image.ImageUtils;
11 |
12 | /**
13 | * The default implementation that takes a screenshot of only the viewport visible to the user from the top page. In
14 | * contrast to {@link ViewportOnlyScreenshot}, images are resized to maximum 800px (see
15 | * {@link #DEFAULT_WANTED_WIDTH_PX}) if they exceed this width, while keeping the aspect ratio.
16 | */
17 | public class ViewportOnlyMinimalScreenshot implements ScreenshotProvider {
18 |
19 | static final int DEFAULT_WANTED_WIDTH_PX = 800;
20 | private static final String RESIZE_MAX_WIDTH_PX = "de.retest.recheck.web.screenshot.maxWidthPx";
21 | private static final int WANTED_WIDTH = Integer.getInteger( RESIZE_MAX_WIDTH_PX, DEFAULT_WANTED_WIDTH_PX );
22 | private static final boolean USE_DEVICE_PIXEL_RATIO = true;
23 |
24 | @Override
25 | public BufferedImage shoot( final WebDriver driver ) {
26 | return resizeImage( Shutterbug.shootPage( driver, USE_DEVICE_PIXEL_RATIO ).getImage() );
27 | }
28 |
29 | @Override
30 | public BufferedImage shoot( final WebDriver driver, final WebElement element ) {
31 | return resizeImage( Shutterbug.shootElement( driver, element, USE_DEVICE_PIXEL_RATIO ).getImage() );
32 | }
33 |
34 | public static BufferedImage resizeImage( final BufferedImage image ) {
35 | if ( image.getWidth() <= WANTED_WIDTH ) {
36 | return image;
37 | }
38 | final double height = image.getHeight() * ((double) WANTED_WIDTH / image.getWidth());
39 | return ImageUtils.resizeImage( image, WANTED_WIDTH, (int) height );
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/resources/pages/covered-page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 | bar baz
64 |
65 |
66 | bar baz
67 |
68 |
69 |
70 |
71 |
72 | Click
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/AutocheckingWebElementIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatCode;
4 | import static org.mockito.Mockito.spy;
5 |
6 | import java.nio.file.Path;
7 |
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.extension.ExtendWith;
11 | import org.junit.jupiter.api.io.TempDir;
12 | import org.openqa.selenium.interactions.Actions;
13 |
14 | import de.retest.recheck.RecheckOptions;
15 | import de.retest.recheck.junit.jupiter.RecheckExtension;
16 | import de.retest.recheck.persistence.SeparatePathsProjectLayout;
17 | import de.retest.web.testutils.PageFactory;
18 | import de.retest.web.testutils.WebDriverFactory;
19 | import de.retest.web.testutils.WebDriverFactory.Driver;
20 |
21 | @ExtendWith( RecheckExtension.class )
22 | class AutocheckingWebElementIT {
23 |
24 | AutocheckingRecheckDriver driver;
25 | AutocheckingWebElement cut;
26 |
27 | @BeforeEach
28 | void setUp( @TempDir Path temp ) {
29 | driver = new AutocheckingRecheckDriver( WebDriverFactory.driver( Driver.CHROME ), RecheckOptions.builder() //
30 | // No Golden Master should be created with this test, thus it will never fail in Recheck#capTest
31 | .projectLayout( new SeparatePathsProjectLayout( temp.resolve( "states" ), temp.resolve( "reports" ) ) ) //
32 | .build() );
33 |
34 | driver.skipCheck().get( PageFactory.page( PageFactory.Page.CENTER ) );
35 |
36 | cut = spy( driver.findElement( By.id( "center" ) ) );
37 | }
38 |
39 | @Test
40 | void actions_should_be_able_to_move_to_element() {
41 | final Actions actions = new Actions( driver ) //
42 | .moveToElement( cut );
43 |
44 | // Apparently, this does not call Location#getCoordinates
45 | assertThatCode( actions::perform ).doesNotThrowAnyException();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/driver/capabilities/CapabilityMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import java.util.Map;
4 |
5 | import org.openqa.selenium.Capabilities;
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.remote.RemoteWebDriver;
8 |
9 | import de.retest.recheck.meta.MetadataProvider;
10 | import de.retest.recheck.meta.MultiMetadataProvider;
11 | import de.retest.web.util.SeleniumWrapperUtil;
12 | import de.retest.web.util.SeleniumWrapperUtil.WrapperOf;
13 | import lombok.extern.slf4j.Slf4j;
14 |
15 | @Slf4j
16 | public final class CapabilityMetadataProvider implements MetadataProvider {
17 |
18 | private final MetadataProvider provider;
19 |
20 | CapabilityMetadataProvider( final Capabilities capabilities ) {
21 | provider = MultiMetadataProvider.of( //
22 | new BrowserMetadataProvider( capabilities ), //
23 | new PlatformMetadataProvider( capabilities ) //
24 | );
25 | }
26 |
27 | public static MetadataProvider of( final WebDriver driver ) {
28 | if ( SeleniumWrapperUtil.isWrapper( WrapperOf.DRIVER, driver ) ) {
29 | return of( (WebDriver) SeleniumWrapperUtil.getWrapped( WrapperOf.DRIVER, driver ) );
30 | }
31 | if ( driver instanceof RemoteWebDriver ) {
32 | return of( (RemoteWebDriver) driver );
33 | }
34 | log.debug( "Cannot retrieve capabilities from driver {}. Driver must be of '{}'. Returning empty metadata.",
35 | driver, RemoteWebDriver.class );
36 | return MetadataProvider.empty();
37 | }
38 |
39 | public static CapabilityMetadataProvider of( final RemoteWebDriver driver ) {
40 | return of( driver.getCapabilities() );
41 | }
42 |
43 | private static CapabilityMetadataProvider of( final Capabilities capabilities ) {
44 | return new CapabilityMetadataProvider( capabilities );
45 | }
46 |
47 | @Override
48 | public Map retrieve() {
49 | return provider.retrieve();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/UnbreakableDriverIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatCode;
4 |
5 | import java.nio.file.Path;
6 |
7 | import org.junit.jupiter.api.AfterEach;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.io.TempDir;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.RecheckOptions;
14 | import de.retest.recheck.persistence.SeparatePathsProjectLayout;
15 | import de.retest.web.RecheckWebImpl;
16 | import de.retest.web.RecheckWebOptions;
17 | import de.retest.web.testutils.WebDriverFactory;
18 |
19 | class UnbreakableDriverIT {
20 |
21 | RecheckWebImpl re;
22 | WebDriver driver;
23 |
24 | @BeforeEach
25 | void setUp( @TempDir final Path project ) {
26 | final Path goldenMasterDirectory = project.resolve( "state" );
27 | final Path reportDirectory = project.resolve( "report" );
28 | final RecheckOptions options = RecheckWebOptions.builder()
29 | .projectLayout( new SeparatePathsProjectLayout( goldenMasterDirectory, reportDirectory ) ) //
30 | .build();
31 |
32 | re = new RecheckWebImpl( options );
33 | driver = new UnbreakableDriver( WebDriverFactory.driver( WebDriverFactory.Driver.CHROME ) );
34 | }
35 |
36 | @AfterEach
37 | void tearDown() {
38 | driver.quit();
39 | re.cap();
40 | }
41 |
42 | @Test
43 | void check_with_web_element_should_properly_identify_the_unbreakable_driver() {
44 | re.startTest();
45 |
46 | driver.get( getClass().getResource( "UnbreakableDriverIT.html" ).toExternalForm() );
47 |
48 | assertThatCode( () -> re.check( driver.findElement( By.id( "html" ) ), "element" ) ).doesNotThrowAnyException();
49 |
50 | capTestSilently();
51 | }
52 |
53 | private void capTestSilently() {
54 | try {
55 | re.capTest();
56 | } catch ( final AssertionError e ) {
57 | // expected
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SimplePageDiffIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.junit.jupiter.api.Assertions.fail;
5 |
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.openqa.selenium.WebDriver;
10 |
11 | import de.retest.recheck.Recheck;
12 | import de.retest.recheck.RecheckImpl;
13 | import de.retest.web.testutils.PageFactory;
14 | import de.retest.web.testutils.WebDriverFactory;
15 | import de.retest.web.testutils.WebDriverFactory.Driver;
16 |
17 | public class SimplePageDiffIT {
18 |
19 | WebDriver driver;
20 | Recheck re;
21 |
22 | @Before
23 | public void setup() {
24 | re = new RecheckImpl();
25 | driver = WebDriverFactory.driver( Driver.CHROME );
26 | }
27 |
28 | @Test
29 | public void testSimpleChange() throws Exception {
30 | re.startTest();
31 |
32 | driver.get( PageFactory.toPageUrlString( "simple-page-diff.html" ) );
33 |
34 | re.check( driver, "open" );
35 | try {
36 | re.capTest();
37 | } catch ( final AssertionError e ) {
38 | assertThat( e ).hasMessageContaining( "Test 'testSimpleChange' has 5 difference(s) in 1 state(s):" ) //
39 | .hasMessageEndingWith( "div (twoblocks) at 'html[1]/body[1]/div[3]':\n" //
40 | + "\t\tid: expected=\"twoblocks\", actual=\"changedblock\"\n" //
41 | + "\tp (some_text) at 'html[1]/body[1]/div[3]/p[1]':\n" //
42 | + "\t\ttext: expected=\"Some text\", actual=\"Some changed text\"\n" //
43 | + "\tp (some_more_text) at 'html[1]/body[1]/div[3]/p[2]':\n" //
44 | + "\t\twas deleted\n" //
45 | + "\tspan (span-2) at 'html[1]/body[1]/div[5]/span[1]':\n" //
46 | + "\t\tcovered: expected=\"true\", actual=\"null\"\n" //
47 | + "\th2 (subheading) at 'html[1]/body[1]/h2[1]':\n" //
48 | + "\t\twas inserted" );
49 | }
50 | }
51 |
52 | @After
53 | public void tearDown() {
54 | driver.quit();
55 | re.cap();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/TabIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.Test;
9 | import org.junit.jupiter.api.extension.ExtendWith;
10 | import org.openqa.selenium.By;
11 | import org.openqa.selenium.WebDriver;
12 |
13 | import de.retest.recheck.junit.jupiter.RecheckExtension;
14 | import de.retest.web.selenium.RecheckDriver;
15 | import de.retest.web.testutils.WebDriverFactory;
16 |
17 | @ExtendWith( RecheckExtension.class )
18 | class TabIT {
19 |
20 | WebDriver driver;
21 |
22 | @BeforeEach
23 | void setUp() {
24 | driver = new RecheckDriver( WebDriverFactory.driver( WebDriverFactory.Driver.CHROME ) );
25 | }
26 |
27 | @AfterEach
28 | void tearDown() {
29 | driver.quit();
30 | }
31 |
32 | @Test
33 | void tab() {
34 | driver.get( TabIT.class.getResource( "page.html" ).toExternalForm() );
35 |
36 | driver.findElement( By.id( "tab" ) ).click();
37 |
38 | selectWindow( driver, 1 );
39 | driver.findElement( By.id( "hello" ) ).click();
40 | closeWindow( driver, 1 );
41 | }
42 |
43 | @Test
44 | void window() {
45 | driver.get( TabIT.class.getResource( "page.html" ).toExternalForm() );
46 |
47 | driver.findElement( By.id( "window" ) ).click();
48 |
49 | selectWindow( driver, 1 );
50 | driver.findElement( By.id( "hello" ) ).click();
51 | closeWindow( driver, 1 );
52 | }
53 |
54 | void selectWindow( final WebDriver driver, final int index ) {
55 | driver.switchTo().window( getWindowHandle( driver, index ) );
56 | }
57 |
58 | String getWindowHandle( final WebDriver driver, final int index ) {
59 | final List tabs = new ArrayList<>( driver.getWindowHandles() );
60 | return tabs.get( index );
61 | }
62 |
63 | void closeWindow( final WebDriver driver, final int index ) {
64 | driver.close();
65 | if ( index > 0 ) {
66 | selectWindow( driver, index - 1 );
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/AutocheckingTargetLocatorTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.mockito.Mockito.mock;
4 | import static org.mockito.Mockito.only;
5 | import static org.mockito.Mockito.verify;
6 |
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.Test;
9 | import org.openqa.selenium.WebDriver;
10 |
11 | class AutocheckingTargetLocatorTest {
12 |
13 | WebDriver.TargetLocator delegate;
14 | AutocheckingRecheckDriver driver;
15 |
16 | AutocheckingTargetLocator cut;
17 |
18 | @BeforeEach
19 | void setUp() {
20 | delegate = mock( WebDriver.TargetLocator.class );
21 | driver = mock( AutocheckingRecheckDriver.class );
22 |
23 | cut = new AutocheckingTargetLocator( delegate, driver );
24 | }
25 |
26 | @Test
27 | void frame_int_should_call_delegate_method() {
28 | cut.frame( 0 );
29 |
30 | verify( delegate, only() ).frame( 0 );
31 | }
32 |
33 | @Test
34 | void frame_string_should_call_delegate_method() {
35 | cut.frame( "name" );
36 |
37 | verify( delegate, only() ).frame( "name" );
38 | }
39 |
40 | @Test
41 | void parentFrame_should_call_delegate_method() {
42 | cut.parentFrame();
43 |
44 | verify( delegate, only() ).parentFrame();
45 | }
46 |
47 | @Test
48 | void window_should_call_delegate_method() {
49 | cut.window( "name" );
50 |
51 | verify( delegate, only() ).window( "name" );
52 | }
53 |
54 | @Test
55 | void defaultContent_should_call_delegate_method() {
56 | cut.defaultContent();
57 |
58 | verify( delegate, only() ).defaultContent();
59 | }
60 |
61 | @Test
62 | void activeElement_should_call_delegate_method() {
63 | cut.activeElement();
64 |
65 | verify( delegate, only() ).activeElement();
66 | }
67 |
68 | @Test
69 | void alert_should_call_delegate_method() {
70 | cut.alert();
71 |
72 | verify( delegate, only() ).alert();
73 | }
74 |
75 | @Test
76 | void window_should_perform_check() {
77 | cut.window( "name" );
78 |
79 | verify( driver, only() ).check( "switch-window" );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/mapping/WebDataFilterTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.mapping;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.mock;
5 | import static org.mockito.Mockito.when;
6 |
7 | import java.util.stream.Stream;
8 |
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.params.ParameterizedTest;
11 | import org.junit.jupiter.params.provider.MethodSource;
12 |
13 | import de.retest.web.mapping.WebData;
14 | import de.retest.web.mapping.WebDataFilter;
15 |
16 | class WebDataFilterTest {
17 |
18 | @Test
19 | void should_ignore_invisible_elements() {
20 | final WebData webData = createInvisibleWebDataForTag( "a" );
21 | assertThat( WebDataFilter.shouldIgnore( webData ) ).isTrue();
22 | }
23 |
24 | @Test
25 | void should_not_ignore_visible_elements() throws Exception {
26 | final WebData webData = createVisibleWebDataForTag( "a" );
27 | assertThat( WebDataFilter.shouldIgnore( webData ) ).isFalse();
28 | }
29 |
30 | @ParameterizedTest
31 | @MethodSource( "specialTags" )
32 | void testName( final String specialTag ) throws Exception {
33 | final WebData webData = createVisibleWebDataForTag( specialTag );
34 | assertThat( WebDataFilter.shouldIgnore( webData ) ).isFalse();
35 | }
36 |
37 | private WebData createInvisibleWebDataForTag( final String tagName ) {
38 | final WebData webData = createWebDataMock( tagName );
39 | when( webData.isShown() ).thenReturn( false );
40 | return webData;
41 | }
42 |
43 | private WebData createVisibleWebDataForTag( final String tagName ) {
44 | final WebData webData = createWebDataMock( tagName );
45 | when( webData.isShown() ).thenReturn( true );
46 | return webData;
47 | }
48 |
49 | private WebData createWebDataMock( final String tagName ) {
50 | final WebData webData = mock( WebData.class );
51 | when( webData.getTag() ).thenReturn( tagName );
52 | return webData;
53 | }
54 |
55 | static Stream specialTags() {
56 | return WebDataFilter.specialTags.stream();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/css/RegexTransformerTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium.css;
2 |
3 | import static org.mockito.Mockito.when;
4 |
5 | import org.assertj.core.api.SoftAssertions;
6 | import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
7 | import org.junit.jupiter.api.Test;
8 | import org.junit.jupiter.api.extension.ExtendWith;
9 | import org.mockito.Mock;
10 | import org.mockito.junit.jupiter.MockitoExtension;
11 |
12 | import de.retest.recheck.ui.descriptors.Element;
13 |
14 | @ExtendWith( MockitoExtension.class )
15 | @ExtendWith( SoftAssertionsExtension.class )
16 | class RegexTransformerTest {
17 |
18 | private static final String SELECTOR_PATTERN = "([a-z;]+;)";
19 | private static final String SELECTED_PART = "abc;";
20 | private static final String REMAINING_PART = "b l u b";
21 | private static final String TRIMMED_REMAINING_PART = REMAINING_PART.trim();
22 |
23 | @Mock
24 | private PredicateFactory factory;
25 | @Mock
26 | private Element element;
27 |
28 | @Test
29 | void should_not_match_regex( final SoftAssertions softly ) throws Exception {
30 | final Transformer transformer = RegexTransformer.of( SELECTOR_PATTERN, factory );
31 |
32 | final String selectorString = "002;blub";
33 | final Selector cssSelector = transformer.transform( selectorString );
34 |
35 | softly.assertThat( cssSelector.getPredicate().test( element ) ).isFalse();
36 | softly.assertThat( cssSelector.getRemainingSelector() ).isEqualTo( selectorString );
37 | }
38 |
39 | @Test
40 | void should_match_regex( final SoftAssertions softly ) throws Exception {
41 | when( factory.create( SELECTED_PART ) ).thenReturn( e -> true );
42 | final Transformer transformer = RegexTransformer.of( SELECTOR_PATTERN, factory );
43 |
44 | final Selector cssSelector = transformer.transform( SELECTED_PART + REMAINING_PART );
45 |
46 | softly.assertThat( cssSelector.getPredicate().test( element ) ).isTrue();
47 | softly.assertThat( cssSelector.getRemainingSelector() ).isEqualTo( TRIMMED_REMAINING_PART );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.github/workflows/build-project.yml:
--------------------------------------------------------------------------------
1 | name: Build project
2 |
3 | on: [push, pull_request]
4 |
5 |
6 | env:
7 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
8 | HAS_SONAR_TOKEN: ${{ secrets.SONAR_TOKEN != '' }}
9 |
10 |
11 | jobs:
12 | default:
13 |
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 |
19 | - name: Fetch unshallow to enable blame for Sonar
20 | run: git fetch --prune --unshallow
21 |
22 | - uses: actions/setup-java@v1
23 | with:
24 | java-version: 8
25 |
26 | - name: Cache local Maven repository
27 | uses: actions/cache@v2
28 | with:
29 | path: ~/.m2/repository
30 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
31 | restore-keys: ${{ runner.os }}-maven-
32 |
33 | - name: Cache Sonar
34 | uses: actions/cache@v1
35 | with:
36 | path: ~/.sonar/cache/
37 | key: ${{ runner.os }}-sonar
38 |
39 | - name: Compile
40 | run: mvn ${MVN_ARGS} clean package -DskipTests
41 |
42 | - uses: actions/setup-java@v1
43 | with:
44 | java-version: 15
45 |
46 | - name: Test with SonarCloud
47 | id: test-with-sonar
48 | if: ${{ env.HAS_SONAR_TOKEN == 'true' }}
49 | run: >
50 | mvn ${MVN_ARGS} verify sonar:sonar -Pcoverage
51 | -Dsonar.host.url=https://sonarcloud.io
52 | -Dsonar.organization=retest
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
56 |
57 | - name: Test without SonarCloud
58 | id: test-without-sonar
59 | if: ${{ steps.test-with-sonar.conclusion == 'skipped' }}
60 | run: mvn ${MVN_ARGS} verify
61 |
62 | - name: Archive recheck tests.report
63 | if: always()
64 | uses: actions/upload-artifact@v2
65 | with:
66 | name: tests.report
67 | path: target/test-classes/retest/recheck/tests.report
68 | retention-days: 10
69 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SimpleAutocheckingDriverShowcaseIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import org.junit.After;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 | import org.openqa.selenium.chrome.ChromeDriver;
7 | import org.openqa.selenium.chrome.ChromeOptions;
8 |
9 | import de.retest.web.selenium.AutocheckingRecheckDriver;
10 | import de.retest.web.selenium.By;
11 | import de.retest.web.testutils.PageFactory;
12 | import de.retest.web.testutils.PageFactory.Page;
13 |
14 | /*
15 | * Simple recheck-web showcase for a Chrome-based integration test. See other *IT classes for more examples.
16 | */
17 | public class SimpleAutocheckingDriverShowcaseIT {
18 |
19 | private AutocheckingRecheckDriver driver;
20 |
21 | @Before
22 | public void setup() {
23 | // If ChromeDriver (http://chromedriver.chromium.org/downloads/) is not in your PATH, uncomment this and point to your installation.
24 | // System.setProperty( "webdriver.chrome.driver", "path/to/chromedriver" );
25 |
26 | final ChromeOptions opts = new ChromeOptions();
27 | opts.addArguments(
28 | // Enable headless mode for faster execution.
29 | "--headless",
30 | // Use Chrome in container-based Travis CI enviroment (see https://docs.travis-ci.com/user/chrome#Sandboxing).
31 | "--no-sandbox",
32 | // Fix window size for stable results.
33 | "--window-size=1200,800" );
34 | driver = new AutocheckingRecheckDriver( new ChromeDriver( opts ) );
35 | }
36 |
37 | @Test
38 | public void index() throws Exception {
39 | driver.navigate().to( PageFactory.page( Page.FORM_PAGE ) );
40 |
41 | driver.findElement( By.id( "old-email-id" ) ).sendKeys( "me@retest.de" );
42 |
43 | driver.findElement( By.tagName( "html" ) ).findElement( By.className( "inputLabel" ) ).sendKeys( "typed" );
44 |
45 | driver.findElement( By.name( "checky" ) ).click();
46 |
47 | driver.findElement( By.linkText( "Contact" ) ).click();
48 |
49 | driver.findElement( By.retestId( "contact" ) ).click();
50 |
51 | driver.capTest();
52 | }
53 |
54 | @After
55 | public void tearDown() {
56 | driver.quit();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/RootElementPeer.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import java.awt.image.BufferedImage;
4 |
5 | import de.retest.recheck.ui.DefaultValueFinder;
6 | import de.retest.recheck.ui.descriptors.Element;
7 | import de.retest.recheck.ui.descriptors.IdentifyingAttributes;
8 | import de.retest.recheck.ui.descriptors.MutableAttributes;
9 | import de.retest.recheck.ui.descriptors.RootElement;
10 | import de.retest.recheck.ui.descriptors.idproviders.RetestIdProvider;
11 | import de.retest.recheck.ui.image.ImageUtils;
12 | import de.retest.recheck.ui.image.Screenshot;
13 | import de.retest.web.mapping.WebData;
14 |
15 | public class RootElementPeer extends WebElementPeer {
16 |
17 | private final String title;
18 | private final BufferedImage screenshot;
19 |
20 | public RootElementPeer( final RetestIdProvider retestIdProvider, final WebData webData, final String path,
21 | final String title, final BufferedImage screenshot, final DefaultValueFinder defaultValueFinder ) {
22 | super( retestIdProvider, webData, path, defaultValueFinder );
23 | this.screenshot = screenshot;
24 | this.title = title;
25 | }
26 |
27 | @Override
28 | public RootElement toElement( final Element parent ) {
29 | if ( webData == null ) {
30 | throw new IllegalStateException( "RootElement was not properly initialized!" );
31 | }
32 | final IdentifyingAttributes identifyingAttributes = retrieveIdentifyingAttributes();
33 |
34 | // If this is a WebElement
35 | String screen = title;
36 | String title = this.title;
37 | if ( !identifyingAttributes.getType().equals( "html" ) ) {
38 | screen = "WebElement";
39 | title = identifyingAttributes.getPath();
40 | }
41 |
42 | final MutableAttributes stateAttributes = retrieveStateAttributes( identifyingAttributes );
43 | final String retestId = retestIdProvider.getRetestId( identifyingAttributes );
44 | final Screenshot ss = ImageUtils.image2Screenshot( retestId, screenshot );
45 | final RootElement rootElement =
46 | new RootElement( retestId, identifyingAttributes, stateAttributes.immutable(), ss, screen, 1, title );
47 | rootElement.addChildren( convertChildren( rootElement ) );
48 | return rootElement;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SingleElementIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import java.nio.file.Paths;
4 |
5 | import org.apache.commons.io.FileUtils;
6 | import org.assertj.core.api.Assertions;
7 | import org.junit.jupiter.api.AfterEach;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.openqa.selenium.By;
11 | import org.openqa.selenium.JavascriptExecutor;
12 | import org.openqa.selenium.WebDriver;
13 |
14 | import de.retest.recheck.Recheck;
15 | import de.retest.recheck.RecheckImpl;
16 | import de.retest.recheck.RecheckOptions;
17 | import de.retest.web.testutils.PageFactory;
18 | import de.retest.web.testutils.WebDriverFactory;
19 | import de.retest.web.testutils.WebDriverFactory.Driver;
20 |
21 | class SingleElementIT {
22 |
23 | WebDriver driver;
24 | Recheck re;
25 |
26 | @BeforeEach
27 | public void setup() {
28 | final RecheckOptions reopts = RecheckOptions.builder().ignoreNothing().build();
29 | re = new RecheckImpl( reopts );
30 | driver = WebDriverFactory.driver( Driver.CHROME );
31 | }
32 |
33 | @Test
34 | public void testParentScope() throws Exception {
35 | // create GM
36 | re.startTest( "testParentScope" );
37 | driver.get( PageFactory.toPageUrlString( "single-element-diff.html" ) );
38 | re.check( driver.findElement( By.id( "redtext" ) ), "same-color" );
39 | Assertions.assertThatThrownBy( () -> re.capTest() ).hasMessageContaining( //
40 | "No Golden Master found. First time test was run?" );
41 |
42 | // Check GM has text with red
43 | re.startTest( "testParentScope" );
44 | driver.get( PageFactory.toPageUrlString( "single-element-diff.html" ) );
45 | ((JavascriptExecutor) driver).executeScript( "document.body.style.color = 'green';" );
46 | re.check( driver.findElement( By.id( "redtext" ) ), "same-color" );
47 | Assertions.assertThatThrownBy( () -> re.capTest() ).hasMessageContaining( //
48 | "color: expected=\"rgb(255, 0, 0)\", actual=\"rgb(0, 128, 0)\"" );
49 | }
50 |
51 | @AfterEach
52 | public void tearDown() {
53 | driver.quit();
54 | re.cap();
55 |
56 | // Not standard! Ensure we delete the GM that was created
57 | FileUtils.deleteQuietly(
58 | Paths.get( "src/test/resources/retest/recheck", SingleElementIT.class.getName() ).toFile() );
59 |
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/RootElementPeerNullIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatCode;
4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
5 | import static org.mockito.Mockito.RETURNS_MOCKS;
6 | import static org.mockito.Mockito.any;
7 | import static org.mockito.Mockito.doNothing;
8 | import static org.mockito.Mockito.doReturn;
9 | import static org.mockito.Mockito.mock;
10 | import static org.mockito.Mockito.spy;
11 |
12 | import org.junit.jupiter.api.AfterEach;
13 | import org.junit.jupiter.api.BeforeEach;
14 | import org.junit.jupiter.api.Test;
15 | import org.openqa.selenium.By;
16 | import org.openqa.selenium.WebDriver;
17 | import org.openqa.selenium.WebElement;
18 | import org.openqa.selenium.chrome.ChromeDriver;
19 | import org.openqa.selenium.chrome.ChromeOptions;
20 |
21 | import de.retest.recheck.RecheckImpl;
22 | import de.retest.recheck.ui.descriptors.SutState;
23 |
24 | class RootElementPeerNullIT {
25 |
26 | RecheckImpl re;
27 | WebDriver driver;
28 |
29 | @BeforeEach
30 | void setUp() {
31 | re = spy( new RecheckImpl() );
32 | // Pretend that there is a valid and empty sut state, so it does not get created
33 | doReturn( mock( SutState.class, RETURNS_MOCKS ) ).when( re ).loadExpected( any() );
34 | // Do nothing on cap, i.e do not save the report
35 | doNothing().when( re ).cap();
36 |
37 | final ChromeOptions opts = new ChromeOptions();
38 | opts.setHeadless( true );
39 | opts.addArguments( "--window-size=480,800" );
40 | driver = new ChromeDriver( opts );
41 | }
42 |
43 | @AfterEach
44 | void tearDown() {
45 | driver.quit();
46 | re.cap();
47 | }
48 |
49 | @Test
50 | void root_element_invisible_should_not_cause_npe() throws Exception {
51 | re.startTest();
52 |
53 | driver.get( getClass().getResource( "RootElementPeerNullTest.html" ).toExternalForm() );
54 |
55 | final WebElement invisibleElement = driver.findElement( By.className( "md-tabs" ) );
56 |
57 | assertThatCode( () -> re.check( invisibleElement, "tabs" ) ).doesNotThrowAnyException();
58 |
59 | assertThatThrownBy( () -> re.capTest() ) //
60 | .isInstanceOf( AssertionError.class ) //
61 | .hasMessageContaining( "nav (html[1]/body[1]/div[1]/nav[1]) at 'html[1]/body[1]/div[1]/nav[1]':\n" //
62 | + "\t\twas inserted" );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SimpleRecheckShowcaseIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import java.nio.file.Path;
4 | import java.nio.file.Paths;
5 |
6 | import org.junit.After;
7 | import org.junit.Before;
8 | import org.junit.Rule;
9 | import org.junit.Test;
10 | import org.openqa.selenium.WebDriver;
11 | import org.openqa.selenium.chrome.ChromeDriver;
12 | import org.openqa.selenium.chrome.ChromeOptions;
13 |
14 | import de.retest.recheck.Recheck;
15 | import de.retest.recheck.RecheckImpl;
16 | import de.retest.recheck.junit.vintage.RecheckRule;
17 |
18 | /*
19 | * Simple recheck-web showcase for a Chrome-based integration test. See other *IT classes for more examples.
20 | */
21 | public class SimpleRecheckShowcaseIT {
22 | // Connects recheck to your test's life cycle, taking care of starting and finishing tests, naming them, etc.
23 | @Rule
24 | public RecheckRule rule = new RecheckRule();
25 |
26 | private WebDriver driver;
27 | private Recheck re;
28 |
29 | @Before
30 | public void setup() {
31 | // If ChromeDriver (http://chromedriver.chromium.org/downloads/) is not in your PATH, uncomment this and point to your installation.
32 | // System.setProperty( "webdriver.chrome.driver", "path/to/chromedriver" );
33 |
34 | final ChromeOptions opts = new ChromeOptions();
35 | opts.addArguments(
36 | // Enable headless mode for faster execution.
37 | "--headless",
38 | // Use Chrome in container-based Travis CI enviroment (see https://docs.travis-ci.com/user/chrome#Sandboxing).
39 | "--no-sandbox",
40 | // Fix window size for stable results.
41 | "--window-size=1200,800" );
42 | driver = new ChromeDriver( opts );
43 |
44 | // Use the default implementation.
45 | re = new RecheckImpl();
46 | // Let the recheck rule know which implementation it shall use.
47 | rule.use( re );
48 | }
49 |
50 | @Test
51 | public void simpleShowcase() throws Exception {
52 | // Do your Selenium stuff.
53 | final Path showcasePath = Paths.get( "src/test/resources/pages/showcase/retest.html" );
54 | driver.get( showcasePath.toUri().toURL().toString() );
55 |
56 | Thread.sleep( 1000 );
57 |
58 | // Single call instead of multiple assertions (doesn't fail on differences).
59 | re.check( driver, "index" );
60 | }
61 |
62 | @After
63 | public void tearDown() {
64 | driver.quit();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/resources/pages/showcase/assets/jquery.freeow.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Freeow!
3 | * Stylish, Growl-like message boxes
4 | *
5 | * Copyright (c) 2012 PJ Dietz
6 | * Version: 1.0.2
7 | * Modified: 2012-05-03
8 | * Licensed under the MIT license:
9 | * http://www.opensource.org/licenses/mit-license.php
10 | *
11 | * http://pjdietz.com/jquery-plugins/freeow/
12 | */
13 | (function($){"use strict";var Freeow;Freeow=function(title,message,options){var startStyle,i,u;this.options=$.extend({},$.fn.freeow.defaults,options);this.element=$(this.options.template(title,message));if(this.options.startStyle){startStyle=this.options.startStyle;}
14 | else{startStyle=this.options.hideStyle;}
15 | this.element.css(startStyle);this.element.data("freeow",this);for(i=0,u=this.options.classes.length;i0){this.autoHide=true;self=this;delay=this.options.autoHideDelay;fn=function(){if(self.autoHide){self.hide();}};opts.complete=function(){setTimeout(fn,delay);};}
18 | this.element.animate(this.options.showStyle,opts);},hide:function(){var self=this;this.element.animate(this.options.hideStyle,{duration:this.options.hideDuration,complete:function(){self.destroy();}});},destroy:function(){this.element.data("freeow",undefined);this.element.remove();}};if(typeof $.fn.freeow==="undefined"){$.fn.extend({freeow:function(title,message,options){return this.each(function(){var f;f=new Freeow(title,message,options);f.attach(this);});}});$.fn.freeow.defaults={autoHide:true,autoHideDelay:3000,classes:[],prepend:true,startStyle:null,showStyle:{opacity:1.0},showDuration:250,hideStyle:{opacity:0.0},hideDuration:500,onClick:function(event){$(this).data("freeow").hide();},onHover:function(event){$(this).data("freeow").autoHide=false;},template:function(title,message){var e;e=['','
','
','
'+title+' ','
'+message+'
','
','
','
','
','
'].join("");return e;}};}}(jQuery));
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/meta/SeleniumMetadataProvider.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import org.openqa.selenium.WebDriver;
7 | import org.openqa.selenium.WebElement;
8 |
9 | import de.retest.recheck.meta.MetadataProvider;
10 | import de.retest.recheck.meta.MultiMetadataProvider;
11 | import de.retest.web.meta.driver.WebDriverMetadataProvider;
12 | import de.retest.web.meta.element.WebElementMetadataProvider;
13 | import de.retest.web.util.SeleniumWrapperUtil;
14 | import de.retest.web.util.SeleniumWrapperUtil.WrapperOf;
15 | import lombok.AccessLevel;
16 | import lombok.RequiredArgsConstructor;
17 |
18 | @RequiredArgsConstructor( access = AccessLevel.PACKAGE )
19 | public final class SeleniumMetadataProvider implements MetadataProvider {
20 |
21 | public static final String TYPE_DRIVER = "driver";
22 | public static final String TYPE_ELEMENT = "element";
23 |
24 | private final String type;
25 |
26 | public static MetadataProvider of( final Object object ) {
27 | if ( object instanceof WebElement ) {
28 | return of( (WebElement) object );
29 | }
30 | if ( SeleniumWrapperUtil.isWrapper( WrapperOf.ELEMENT, object ) ) {
31 | return of( SeleniumWrapperUtil.getWrapped( WrapperOf.ELEMENT, object ) );
32 | }
33 | if ( object instanceof WebDriver ) {
34 | return of( (WebDriver) object );
35 | }
36 | if ( SeleniumWrapperUtil.isWrapper( WrapperOf.DRIVER, object ) ) {
37 | return of( SeleniumWrapperUtil.getWrapped( WrapperOf.DRIVER, object ) );
38 | }
39 | throw new IllegalArgumentException(
40 | String.format( "Cannot retrieve metadata from objects of type '%s'.", object.getClass().getName() ) );
41 | }
42 |
43 | private static MetadataProvider of( final WebDriver object ) {
44 | return MultiMetadataProvider.of( //
45 | WebDriverMetadataProvider.of( object ), //
46 | new SeleniumMetadataProvider( TYPE_DRIVER ) //
47 | );
48 | }
49 |
50 | private static MetadataProvider of( final WebElement object ) {
51 | return MultiMetadataProvider.of( //
52 | WebElementMetadataProvider.of( object ), //
53 | new SeleniumMetadataProvider( TYPE_ELEMENT ) //
54 | );
55 | }
56 |
57 | @Override
58 | public Map retrieve() {
59 | final Map map = new HashMap<>();
60 |
61 | map.put( SeleniumMetadata.CHECK_TYPE, type );
62 |
63 | return map;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/RecheckWebImplIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import static org.assertj.core.api.Assertions.assertThatCode;
4 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
5 |
6 | import java.nio.file.Path;
7 |
8 | import org.junit.jupiter.api.AfterEach;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.io.TempDir;
12 |
13 | import de.retest.recheck.RecheckLifecycle;
14 | import de.retest.recheck.RecheckOptions;
15 | import de.retest.recheck.persistence.SeparatePathsProjectLayout;
16 | import de.retest.web.selenium.RecheckDriver;
17 | import de.retest.web.testutils.WebDriverFactory;
18 | import de.retest.web.testutils.WebDriverFactory.Driver;
19 |
20 | class RecheckWebImplIT {
21 |
22 | RecheckWebImpl re;
23 |
24 | RecheckDriver driver;
25 |
26 | @BeforeEach
27 | void setUp( @TempDir final Path project ) {
28 | final Path goldenMasterDirectory = project.resolve( "state" );
29 | final Path reportDirectory = project.resolve( "report" );
30 | final RecheckOptions options = RecheckWebOptions.builder()
31 | .projectLayout( new SeparatePathsProjectLayout( goldenMasterDirectory, reportDirectory ) ) //
32 | .build();
33 |
34 | re = new RecheckWebImpl( options );
35 | driver = new RecheckDriver( WebDriverFactory.driver( Driver.CHROME ), options );
36 | }
37 |
38 | @AfterEach
39 | void tearDown() {
40 | driver.quit();
41 | re.cap();
42 | }
43 |
44 | @Test
45 | void check_should_throw_if_explicit_checking() throws Exception {
46 | re.startTest();
47 | driver.startTest();
48 |
49 | assertThatThrownBy( () -> re.check( driver, "explicit-implicit-mixed" ) ) //
50 | .isInstanceOf( UnsupportedOperationException.class ) //
51 | .hasMessageStartingWith( "The 'RecheckDriver' does implicit checking after each action, "
52 | + "therefore no explicit check with 'Recheck#check' is needed" );
53 |
54 | capTestSilently( re );
55 | capTestSilently( driver );
56 | }
57 |
58 | @Test
59 | void check_should_not_throw_if_implicit_checking() throws Exception {
60 | driver.startTest();
61 |
62 | assertThatCode( () -> driver.get( "https://retest.de" ) ).doesNotThrowAnyException();
63 |
64 | capTestSilently( driver );
65 | }
66 |
67 | private void capTestSilently( final RecheckLifecycle re ) {
68 | try {
69 | re.capTest();
70 | } catch ( final AssertionError e ) {
71 | // expected
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/css/PredicateBuilder.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium.css;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 | import java.util.Optional;
6 | import java.util.function.Predicate;
7 |
8 | import de.retest.recheck.ui.descriptors.Element;
9 | import lombok.extern.slf4j.Slf4j;
10 |
11 | @Slf4j
12 | public class PredicateBuilder {
13 |
14 | private final List selectors;
15 | private final List> predicates;
16 | private final String origSelector;
17 |
18 | public PredicateBuilder( final List selectors, final String origSelector ) {
19 | this.selectors = selectors;
20 | this.origSelector = origSelector;
21 | predicates = new LinkedList<>();
22 | }
23 |
24 | public Optional> build() {
25 | final String remainingSelector = parse( origSelector );
26 |
27 | if ( isPartAvailable( remainingSelector ) ) {
28 | logUnkownSelector( remainingSelector );
29 | return Optional.empty();
30 |
31 | }
32 | return Optional.of( combinePredicates() );
33 | }
34 |
35 | private String parse( final String selector ) {
36 | String oldSelector = "";
37 | String remainingSelector = selector;
38 | while ( isPartAvailable( remainingSelector ) && !oldSelector.equals( remainingSelector ) ) {
39 | oldSelector = remainingSelector;
40 | remainingSelector = transform( remainingSelector );
41 | }
42 | return remainingSelector;
43 | }
44 |
45 | private String transform( final String selector ) {
46 | String remainingSelector = selector;
47 | for ( final Transformer function : selectors ) {
48 | final Selector cssSelector = function.transform( remainingSelector );
49 | remainingSelector = cssSelector.getRemainingSelector();
50 | if ( !selector.equals( remainingSelector ) ) {
51 | predicates.add( cssSelector.getPredicate() );
52 | return remainingSelector;
53 | }
54 | }
55 | return remainingSelector;
56 | }
57 |
58 | private boolean isPartAvailable( final String selector ) {
59 | return !selector.isEmpty();
60 | }
61 |
62 | private void logUnkownSelector( final String selector ) {
63 | log.warn(
64 | "Unbreakable tests are not implemented for all CSS selectors. Please report your chosen selector ('{}') at https://github.com/retest/recheck-web/issues.",
65 | selector );
66 | }
67 |
68 | private Predicate combinePredicates() {
69 | return predicates.stream().reduce( e -> true, Predicate::and );
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/css/DefaultSelectors.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium.css;
2 |
3 | import java.util.LinkedList;
4 | import java.util.List;
5 |
6 | public class DefaultSelectors {
7 |
8 | private static final String CHARACTERSET = "a-zA-Z0-9\\-_";
9 | private static final String ALLOWED_CHARACTERS = "[" + CHARACTERSET + "]+";
10 | private static final String TAG_PATTERN = "(" + ALLOWED_CHARACTERS + ")";
11 | private static final String ID_PATTERN = "\\#(" + ALLOWED_CHARACTERS + ")";
12 | private static final String CLASS_PATTERN = "\\.(" + ALLOWED_CHARACTERS + ")";
13 | private static final String ATTRIBUTE_PATTERN = attributePattern( "" );
14 | private static final String ATTRIBUTE_CONTAINING_PATTERN = attributePattern( "~" );
15 | private static final String ATTRIBUTE_STARTING_PATTERN = attributePattern( "\\|" );
16 | private static final String ATTRIBUTE_BEGINNING_PATTERN = attributePattern( "\\^" );
17 | private static final String ATTRIBUTE_ENDING_PATTERN = attributePattern( "\\$" );
18 | private static final String ATTRIBUTE_CONTAINING_SUBSTRING_PATTERN = attributePattern( "\\*" );
19 | private static final String PSEUDO_CLASS_PATTERN = ":((?!not\\()" + ALLOWED_CHARACTERS + ")";
20 |
21 | private static String attributePattern( final String selectorChar ) {
22 | return "\\[([" + CHARACTERSET + selectorChar + " =\"']+)\\]";
23 | }
24 |
25 | private DefaultSelectors() {}
26 |
27 | public static List all() {
28 | final List transformers = new LinkedList<>();
29 | transformers.add( RegexTransformer.of( TAG_PATTERN, Has::cssTag ) );
30 | transformers.add( RegexTransformer.of( ID_PATTERN, Has::cssId ) );
31 | transformers.add( RegexTransformer.of( CLASS_PATTERN, Has::cssClass ) );
32 | transformers.add( RegexTransformer.of( ATTRIBUTE_PATTERN, Has::attribute ) );
33 | transformers.add( RegexTransformer.of( ATTRIBUTE_CONTAINING_PATTERN, Has::attributeContaining ) );
34 | transformers.add( RegexTransformer.of( ATTRIBUTE_STARTING_PATTERN, Has::attributeStarting ) );
35 | transformers.add( RegexTransformer.of( ATTRIBUTE_BEGINNING_PATTERN, Has::attributeBeginning ) );
36 | transformers.add( RegexTransformer.of( ATTRIBUTE_ENDING_PATTERN, Has::attributeEnding ) );
37 | transformers.add( RegexTransformer.of( ATTRIBUTE_CONTAINING_SUBSTRING_PATTERN, Has::attributeContainingSubstring ) );
38 | transformers.add( RegexTransformer.of( PSEUDO_CLASS_PATTERN, Has::cssPseudoClass ) );
39 | return transformers;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/SeleniumMetadataProviderTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.mockito.Mockito.RETURNS_MOCKS;
5 | import static org.mockito.Mockito.mock;
6 | import static org.mockito.Mockito.when;
7 |
8 | import org.apache.commons.lang3.tuple.Pair;
9 | import org.junit.jupiter.api.Test;
10 | import org.openqa.selenium.WebDriver;
11 | import org.openqa.selenium.WebElement;
12 | import org.openqa.selenium.WrapsDriver;
13 | import org.openqa.selenium.WrapsElement;
14 | import org.openqa.selenium.remote.RemoteWebDriver;
15 | import org.openqa.selenium.remote.RemoteWebElement;
16 |
17 | import de.retest.recheck.meta.MetadataProvider;
18 |
19 | class SeleniumMetadataProviderTest {
20 |
21 | @Test
22 | void retrieve_should_identify_driver() throws Exception {
23 | final WebDriver driver = mock( RemoteWebDriver.class, RETURNS_MOCKS );
24 |
25 | final MetadataProvider cut = SeleniumMetadataProvider.of( driver );
26 |
27 | assertThat( cut.retrieve() ) //
28 | .contains( Pair.of( SeleniumMetadata.CHECK_TYPE, SeleniumMetadataProvider.TYPE_DRIVER ) );
29 | }
30 |
31 | @Test
32 | void retrieve_should_identify_wrapped_driver() throws Exception {
33 | final WrapsDriver wrapsDriver = mock( WrapsDriver.class );
34 | when( wrapsDriver.getWrappedDriver() ).thenReturn( mock( RemoteWebDriver.class, RETURNS_MOCKS ) );
35 |
36 | final MetadataProvider cut = SeleniumMetadataProvider.of( wrapsDriver );
37 |
38 | assertThat( cut.retrieve() ) //
39 | .contains( Pair.of( SeleniumMetadata.CHECK_TYPE, SeleniumMetadataProvider.TYPE_DRIVER ) );
40 | }
41 |
42 | @Test
43 | void retrieve_should_identify_element() throws Exception {
44 | final WebElement element = mock( WebElement.class, RETURNS_MOCKS );
45 |
46 | final MetadataProvider cut = SeleniumMetadataProvider.of( element );
47 |
48 | assertThat( cut.retrieve() ) //
49 | .contains( Pair.of( SeleniumMetadata.CHECK_TYPE, SeleniumMetadataProvider.TYPE_ELEMENT ) );
50 | }
51 |
52 | @Test
53 | void retrieve_should_identify_wrapped_element() throws Exception {
54 | final WrapsElement wrapsElement = mock( WrapsElement.class );
55 | when( wrapsElement.getWrappedElement() ).thenReturn( mock( RemoteWebElement.class, RETURNS_MOCKS ) );
56 |
57 | final MetadataProvider cut = SeleniumMetadataProvider.of( wrapsElement );
58 |
59 | assertThat( cut.retrieve() ) //
60 | .contains( Pair.of( SeleniumMetadata.CHECK_TYPE, SeleniumMetadataProvider.TYPE_ELEMENT ) );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/RecheckRemoteWebElementIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import org.junit.jupiter.api.AfterEach;
4 | import org.junit.jupiter.api.BeforeEach;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.junit.jupiter.params.ParameterizedTest;
7 | import org.junit.jupiter.params.provider.MethodSource;
8 | import org.openqa.selenium.JavascriptExecutor;
9 | import org.openqa.selenium.WebDriver;
10 |
11 | import de.retest.recheck.Recheck;
12 | import de.retest.recheck.RecheckImpl;
13 | import de.retest.recheck.junit.jupiter.RecheckExtension;
14 | import de.retest.web.selenium.By;
15 | import de.retest.web.testutils.PageFactory;
16 | import de.retest.web.testutils.PageFactory.Page;
17 |
18 | @ExtendWith( RecheckExtension.class )
19 | class RecheckRemoteWebElementIT {
20 |
21 | WebDriver driver;
22 | Recheck re;
23 |
24 | @BeforeEach
25 | void setUp() {
26 | re = new RecheckImpl();
27 | }
28 |
29 | @ParameterizedTest( name = "findElement-equals-driver-{1}" )
30 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
31 | void html_via_findElement_should_equal_driver_html( final WebDriver driver, final String name ) throws Exception {
32 | this.driver = driver;
33 | driver.get( PageFactory.page( Page.FORM_PAGE ) );
34 |
35 | Thread.sleep( 1000 );
36 |
37 | re.check( driver, "open" );
38 | re.check( driver.findElement( By.tagName( "html" ) ), "open" );
39 | }
40 |
41 | @ParameterizedTest( name = "simple-webelement-{1}" )
42 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
43 | void no_children_webelement_should_be_checked( final WebDriver driver, final String name ) throws Exception {
44 | this.driver = driver;
45 | driver.get( PageFactory.page( Page.FORM_PAGE ) );
46 |
47 | Thread.sleep( 1000 );
48 |
49 | re.check( driver.findElement( By.id( "email" ) ), "open" );
50 | }
51 |
52 | @ParameterizedTest( name = "empty-article-{1}" )
53 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
54 | void empty_article_should_be_checked( final WebDriver driver, final String name ) throws Exception {
55 | this.driver = driver;
56 | driver.get( PageFactory.page( Page.FORM_PAGE ) );
57 |
58 | ((JavascriptExecutor) driver).executeScript(
59 | "document.getElementsByTagName('body')[0].appendChild(document.createElement('article'))" );
60 |
61 | Thread.sleep( 1000 );
62 |
63 | re.check( driver.findElement( By.tagName( "article" ) ), "open" );
64 | }
65 |
66 | @AfterEach
67 | void tearDown() {
68 | driver.quit();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.github/workflows/release-beta.yml:
--------------------------------------------------------------------------------
1 | name: Release beta
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | approval:
7 | description: 'Do you really want to release a BETA from configured BRANCH?'
8 | required: true
9 | default: 'NO'
10 |
11 | env:
12 | MVN_ARGS: --batch-mode --errors --fail-fast --no-transfer-progress
13 |
14 | jobs:
15 | default:
16 |
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - name: Verify approval
21 | run: "[[ $(echo ${{ github.event.inputs.approval }} | tr 'a-z' 'A-Z') == 'YES' ]]"
22 |
23 | - uses: actions/checkout@v2
24 | with:
25 | fetch-depth: 0
26 | token: ${{ secrets.TRIGGER_ACTIONS_GITHUB_TOKEN }}
27 |
28 | - name: Verify for release or hotfix branch
29 | run: "[[ $( git branch --show-current ) =~ ^release.*|^hotfix.* ]]"
30 |
31 | - uses: actions/setup-java@v1
32 | with:
33 | java-version: 8
34 |
35 | - name: Cache local Maven repository
36 | uses: actions/cache@v2
37 | with:
38 | path: ~/.m2/repository
39 | key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
40 | restore-keys: ${{ runner.os }}-maven-
41 |
42 | - name: Configure Git user
43 | run: |
44 | git config user.email "ops+githubactions@retest.de"
45 | git config user.name "retest release github action"
46 |
47 | - id: next_beta
48 | name: Find next beta version
49 | run: |
50 | # get next stable release version from pom.xml
51 | RELEASE_VERSION=$( mvn help:evaluate -Dexpression=project.version -q -DforceStdout | sed 's/-SNAPSHOT//' )
52 | # find last beta for this version using git tags. Prefix with 0 als default for next line
53 | LAST_BETA_TAG=0$( git for-each-ref --sort=-taggerdate --count=1 --format '%(refname:short)' "refs/tags/v${RELEASE_VERSION}-beta.*" )
54 | # Create string for beta version
55 | NEXT_BETA="$RELEASE_VERSION-beta.$((1+"${LAST_BETA_TAG/v$RELEASE_VERSION-beta./}"))"
56 | echo "::set-output name=version::$NEXT_BETA"
57 |
58 | - name: Create beta release tag
59 | run: |
60 | mvn versions:set -DnewVersion=${{ steps.next_beta.outputs.version }} -DgenerateBackupPoms=false
61 | git commit -a -m "ci: release ${{ steps.next_beta.outputs.version }}"
62 | git tag -a v${{ steps.next_beta.outputs.version }} -m "ci: tag beta release ${{ steps.next_beta.outputs.version }}"
63 | git push --tags
64 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/RecheckWebImpl.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import java.io.File;
4 |
5 | import de.retest.recheck.RecheckAdapter;
6 | import de.retest.recheck.RecheckImpl;
7 | import de.retest.recheck.RecheckOptions;
8 | import de.retest.recheck.ui.descriptors.SutState;
9 | import de.retest.web.selenium.ImplicitDriverWrapper;
10 | import de.retest.web.selenium.UnbreakableDriver;
11 | import de.retest.web.util.SeleniumWrapperUtil;
12 | import de.retest.web.util.SeleniumWrapperUtil.WrapperOf;
13 | import lombok.AccessLevel;
14 | import lombok.Getter;
15 |
16 | /**
17 | * This class is specifically needed in conjunction with the {@link UnbreakableDriver}. For simple explicit calls to
18 | * {@link #check(Object, String)}, a {@link RecheckImpl} suffices.
19 | */
20 | public class RecheckWebImpl extends RecheckImpl {
21 |
22 | @Getter( AccessLevel.PACKAGE )
23 | private UnbreakableDriver driver;
24 |
25 | public RecheckWebImpl() {
26 | super();
27 | }
28 |
29 | public RecheckWebImpl( final RecheckOptions opts ) {
30 | super( opts );
31 | }
32 |
33 | @Override
34 | public void check( final Object driver, final RecheckAdapter seleniumAdapter, final String currentStep ) {
35 | this.driver = retrieveUnbreakableDriver( driver );
36 | super.check( driver, seleniumAdapter, currentStep );
37 | }
38 |
39 | @Override
40 | public void check( final Object driver, final String currentStep ) {
41 | this.driver = retrieveUnbreakableDriver( driver );
42 | super.check( driver, currentStep );
43 | }
44 |
45 | private UnbreakableDriver retrieveUnbreakableDriver( final Object driver ) {
46 | if ( driver instanceof ImplicitDriverWrapper ) {
47 | return retrieveUnbreakableDriver( ((ImplicitDriverWrapper) driver).getWrappedDriver() );
48 | }
49 | if ( driver instanceof UnbreakableDriver ) {
50 | return (UnbreakableDriver) driver;
51 | }
52 | if ( SeleniumWrapperUtil.isWrapper( WrapperOf.DRIVER, driver ) ) {
53 | return retrieveUnbreakableDriver( SeleniumWrapperUtil.getWrapped( WrapperOf.DRIVER, driver ) );
54 | }
55 | return null;
56 | }
57 |
58 | @Override
59 | public SutState loadExpected( final File file ) {
60 | final SutState result = super.loadExpected( file );
61 | if ( driver == null ) {
62 | throw new IllegalStateException(
63 | "Must first call a check-method with an UnbreakableDriver before being able to load a Golden Master (needed for unbreakable tests)!" );
64 | }
65 | if ( result != null ) {
66 | driver.setLastExpectedState( result.getRootElements().get( 0 ) );
67 | }
68 | return result;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/RootElementPeerNullTest.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Welcome - retest docs
11 |
12 |
13 |
14 |
15 |
16 |
40 |
41 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/screenshot/ScreenshotProviders.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.screenshot;
2 |
3 | import static de.retest.recheck.ui.image.ImageUtils.extractScale;
4 |
5 | import java.awt.image.BufferedImage;
6 | import java.lang.reflect.Method;
7 |
8 | import org.aeonbits.owner.Converter;
9 | import org.openqa.selenium.WebDriver;
10 | import org.openqa.selenium.WebElement;
11 |
12 | import de.retest.web.RecheckWebOptions;
13 | import de.retest.web.RecheckWebProperties;
14 | import lombok.extern.slf4j.Slf4j;
15 |
16 | @Slf4j
17 | public class ScreenshotProviders {
18 |
19 | /**
20 | * The default screenshot provider: {@link ViewportOnlyMinimalScreenshot}.
21 | */
22 | public static final ScreenshotProvider DEFAULT = new ViewportOnlyMinimalScreenshot();
23 | /**
24 | * The none screenshot provider: {@link NoScreenshot}.
25 | */
26 | public static final ScreenshotProvider NONE = new NoScreenshot();
27 |
28 | public static class ScreenshotProviderConverter implements Converter {
29 |
30 | @Override
31 | public ScreenshotProvider convert( final Method method, final String input ) {
32 | switch ( input ) {
33 | case "fullPage":
34 | return new FullPageScreenshot();
35 | case "viewportOnly":
36 | return new ViewportOnlyScreenshot();
37 | case "viewportOnlyMinimal":
38 | return new ViewportOnlyMinimalScreenshot();
39 | case "none":
40 | log.info( "ScreenshotProvider has been set to 'none' either via property "
41 | + RecheckWebProperties.SCREENSHOT_PROVIDER_PROPERTY_KEY + " or via "
42 | + RecheckWebOptions.class.getSimpleName() + ", will create NO screenshots." );
43 | return NONE;
44 | default:
45 | log.warn( "Unknown configured screenshot provider '{}'. Using default value 'viewportOnlyMinimal'.",
46 | input );
47 | return DEFAULT;
48 | }
49 | }
50 |
51 | }
52 |
53 | public static final int SCALE = extractScale();
54 |
55 | private ScreenshotProviders() {}
56 |
57 | public static BufferedImage shoot( final WebDriver driver, final WebElement element,
58 | final ScreenshotProvider screenshotProvider ) {
59 | try {
60 | final long startTime = System.currentTimeMillis();
61 | if ( element != null ) {
62 | return screenshotProvider.shoot( driver, element );
63 | }
64 | final BufferedImage result = screenshotProvider.shoot( driver );
65 | log.info( "Took {}ms to create the screenshot.", System.currentTimeMillis() - startTime );
66 | return result;
67 | } catch ( final Exception e ) {
68 | log.error( "Exception creating screenshot for check.", e );
69 | return null;
70 | }
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/ByWhispererTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static de.retest.web.selenium.ByWhisperer.retrieveCssClassName;
4 | import static de.retest.web.selenium.ByWhisperer.retrieveCssSelector;
5 | import static de.retest.web.selenium.ByWhisperer.retrieveId;
6 | import static de.retest.web.selenium.ByWhisperer.retrieveLinkText;
7 | import static de.retest.web.selenium.ByWhisperer.retrieveName;
8 | import static de.retest.web.selenium.ByWhisperer.retrievePartialLinkText;
9 | import static de.retest.web.selenium.ByWhisperer.retrieveTag;
10 | import static de.retest.web.selenium.ByWhisperer.retrieveXPath;
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | import org.junit.jupiter.api.Test;
14 | import org.openqa.selenium.By.ByClassName;
15 | import org.openqa.selenium.By.ByCssSelector;
16 | import org.openqa.selenium.By.ById;
17 | import org.openqa.selenium.By.ByLinkText;
18 | import org.openqa.selenium.By.ByName;
19 | import org.openqa.selenium.By.ByPartialLinkText;
20 | import org.openqa.selenium.By.ByTagName;
21 | import org.openqa.selenium.By.ByXPath;
22 |
23 | class ByWhispererTest {
24 |
25 | @Test
26 | void retrieveId_should_return_id() {
27 | final String id = "someId";
28 | assertThat( retrieveId( (ById) By.id( id ) ) ).isEqualTo( id );
29 | }
30 |
31 | @Test
32 | void retrieveCSSClassName_should_return_ClassName() {
33 | final String cssClass = "someClass";
34 | assertThat( retrieveCssClassName( (ByClassName) By.className( cssClass ) ) ).isEqualTo( cssClass );
35 | }
36 |
37 | @Test
38 | void retrieveName_should_return_Name() {
39 | final String name = "someName";
40 | assertThat( retrieveName( (ByName) By.name( name ) ) ).isEqualTo( name );
41 | }
42 |
43 | @Test
44 | void retrieveLinkText_should_return_LinkText() {
45 | final String linkText = "someLinkText";
46 | assertThat( retrieveLinkText( (ByLinkText) By.linkText( linkText ) ) ).isEqualTo( linkText );
47 | }
48 |
49 | @Test
50 | void retrievePartialLinkText_should_return_PartialLinkText() {
51 | final String linkText = "someLinkText";
52 | assertThat( retrievePartialLinkText( (ByPartialLinkText) By.partialLinkText( linkText ) ) )
53 | .isEqualTo( linkText );
54 | }
55 |
56 | @Test
57 | void retrieveCssSelector_should_return_Selector() {
58 | final String selector = "selector";
59 | assertThat( retrieveCssSelector( (ByCssSelector) By.cssSelector( selector ) ) ).isEqualTo( selector );
60 | }
61 |
62 | @Test
63 | void retrieveXPath_should_return_xpath() {
64 | final String xpath = "HTML[1]/DIV[1]";
65 | assertThat( retrieveXPath( (ByXPath) By.xpath( xpath ) ) ).isEqualTo( xpath );
66 | }
67 |
68 | @Test
69 | void retrieveTagName_should_return_tagName() {
70 | final String tag = "div";
71 | assertThat( retrieveTag( (ByTagName) By.tagName( tag ) ) ).isEqualTo( tag );
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/mapping/PathsToWebDataMapping.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.mapping;
2 |
3 | import static org.apache.commons.lang3.StringUtils.countMatches;
4 |
5 | import java.util.Iterator;
6 | import java.util.LinkedHashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Map.Entry;
10 |
11 | import org.openqa.selenium.JavascriptExecutor;
12 |
13 | /**
14 | * Raw paths to web data mapping that is received via {@link JavascriptExecutor#executeScript(String, Object...)}.
15 | * Internally, it is a map of paths to maps of attributes. Example:
16 | *
17 | *
18 | * {
19 | * /foo[1] = { attribute0 = value0, attribute1 = value1, ... }
20 | * /foo[1]/bar[1] = { attribute0 = value0, attribute1 = value1, ... }
21 | * ...
22 | * }
23 | *
24 | */
25 | public class PathsToWebDataMapping implements Iterable> {
26 |
27 | private final LinkedHashMap mapping;
28 | private final String rootPath;
29 |
30 | public PathsToWebDataMapping( final List> mapping ) {
31 | this( "/", mapping );
32 | }
33 |
34 | /**
35 | * @param frameParentPath
36 | * The parent path of the frame.
37 | * @param mapping
38 | * The raw map of paths to maps of attributes.
39 | */
40 | public PathsToWebDataMapping( final String frameParentPath, final List> mapping ) {
41 | rootPath = frameParentPath + extractRootPath( mapping );
42 | this.mapping = convertMapping( frameParentPath, mapping );
43 | }
44 |
45 | @SuppressWarnings( "unchecked" )
46 | private LinkedHashMap convertMapping( final String frameParentPath,
47 | final List> mapping ) {
48 | final LinkedHashMap result = new LinkedHashMap<>();
49 | for ( final List list : mapping ) {
50 | result.put( frameParentPath + list.get( 0 ).toString().replace( "//", "/" ),
51 | new WebData( (Map) list.get( 1 ) ) );
52 | }
53 | return result;
54 | }
55 |
56 | private String extractRootPath( final List> mapping ) {
57 | String rootPath = null;
58 | for ( final List entry : mapping ) {
59 | if ( rootPath == null ) {
60 | rootPath = entry.get( 0 ).toString();
61 | continue;
62 | }
63 | if ( countMatches( entry.get( 0 ).toString(), "/" ) < countMatches( rootPath, "/" ) ) {
64 | rootPath = entry.get( 0 ).toString();
65 | }
66 | }
67 | return rootPath != null ? rootPath.replace( "//", "/" ) : "";
68 | }
69 |
70 | public int size() {
71 | return mapping.size();
72 | }
73 |
74 | public WebData getWebData( final String path ) {
75 | return mapping.get( path );
76 | }
77 |
78 | @Override
79 | public Iterator> iterator() {
80 | return mapping.entrySet().iterator();
81 | }
82 |
83 | public String getRootPath() {
84 | return rootPath;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/eclipse/retest GUI recheck-web Java 1.8.launch:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/RecheckRemoteWebElementFailingIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import static org.junit.jupiter.api.Assertions.fail;
4 |
5 | import org.assertj.core.api.Assertions;
6 | import org.junit.jupiter.api.AfterEach;
7 | import org.junit.jupiter.api.BeforeEach;
8 | import org.junit.jupiter.api.Disabled;
9 | import org.junit.jupiter.params.ParameterizedTest;
10 | import org.junit.jupiter.params.provider.MethodSource;
11 | import org.openqa.selenium.JavascriptExecutor;
12 | import org.openqa.selenium.WebDriver;
13 |
14 | import de.retest.recheck.Recheck;
15 | import de.retest.recheck.RecheckImpl;
16 | import de.retest.web.selenium.By;
17 | import de.retest.web.testutils.PageFactory;
18 | import de.retest.web.testutils.PageFactory.Page;
19 |
20 | class RecheckRemoteWebElementFailingIT {
21 |
22 | private static final String MISSING_ASSERTION_MSG = "An assertion error should have been produced";
23 | private static final String SELECT_ELEMENT = "select-element-";
24 | WebDriver driver;
25 | Recheck re;
26 |
27 | @BeforeEach
28 | void setUp() {
29 | re = new RecheckImpl();
30 | }
31 |
32 | @ParameterizedTest
33 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
34 | @Disabled("Temporarily disabled due to bug")
35 | void inserted_child_in_webelement_should_be_checked( final WebDriver driver ) throws Exception {
36 | this.driver = driver;
37 | driver.get( PageFactory.page( Page.FORM_PAGE ) );
38 |
39 | ((JavascriptExecutor) driver)
40 | .executeScript( "document.getElementById('multi').add(document.createElement('option'))" );
41 |
42 | re.startTest( SELECT_ELEMENT + driver.getClass().getSimpleName() );
43 |
44 | Thread.sleep( 1000 );
45 |
46 | re.check( driver.findElement( By.id( "multi" ) ), "open" );
47 |
48 | try {
49 | re.capTest();
50 | fail( MISSING_ASSERTION_MSG );
51 | } catch ( final AssertionError e ) {
52 | Assertions.assertThat( e )
53 | .hasMessageContaining( "option (option) at 'html[1]/body[1]/form[3]/select[2]/option[5]':\n" //
54 | + "\t\twas inserted" );
55 | }
56 | }
57 |
58 | @ParameterizedTest
59 | @MethodSource( "de.retest.web.testutils.WebDriverFactory#drivers" )
60 | @Disabled("Temporarily disabled due to bug")
61 | void deleted_child_in_webelement_should_be_checked( final WebDriver driver ) throws Exception {
62 | this.driver = driver;
63 | driver.get( PageFactory.page( Page.FORM_PAGE ) );
64 |
65 | ((JavascriptExecutor) driver).executeScript( "document.getElementById('multi').remove(3)" );
66 |
67 | re.startTest( SELECT_ELEMENT + driver.getClass().getSimpleName() );
68 |
69 | Thread.sleep( 1000 );
70 |
71 | re.check( driver.findElement( By.id( "multi" ) ), "open" );
72 |
73 | try {
74 | re.capTest();
75 | fail( MISSING_ASSERTION_MSG );
76 | } catch ( final AssertionError e ) {
77 | Assertions.assertThat( e ).hasMessageContaining(
78 | "option (onion_gravy) at 'html[1]/body[1]/form[3]/select[2]/option[4]':\n was deleted" );
79 | }
80 | }
81 |
82 | @AfterEach
83 | void tearDown() {
84 | driver.quit();
85 | re.cap();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/meta/driver/capabilities/AllCapabilities.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.meta.driver.capabilities;
2 |
3 | import java.util.logging.Logger;
4 | import java.util.stream.Stream;
5 |
6 | import org.openqa.selenium.Capabilities;
7 | import org.openqa.selenium.Platform;
8 | import org.openqa.selenium.remote.BrowserType;
9 | import org.openqa.selenium.remote.CapabilityType;
10 | import org.openqa.selenium.remote.DesiredCapabilities;
11 |
12 | public class AllCapabilities {
13 |
14 | private static final Logger LOG = Logger.getLogger(Capabilities.class.getName());
15 |
16 | public static Stream capabilities() {
17 | return Stream.of( //
18 | android(), //
19 | iphone(), //
20 | ipad(), //
21 | htmlUnit(), //
22 |
23 | chrome(), //
24 | firefox(), //
25 |
26 | edge(), //
27 | internetExplorer(), //
28 |
29 | safari(), //
30 |
31 | operaBlink() //
32 | );
33 | }
34 |
35 | private static Capabilities iphone() {
36 | return new DesiredCapabilities(BrowserType.IPHONE, "", Platform.MAC);
37 | }
38 |
39 | private static Capabilities ipad() {
40 | return new DesiredCapabilities(BrowserType.IPAD, "", Platform.MAC);
41 | }
42 |
43 | private static DesiredCapabilities android() {
44 | return new DesiredCapabilities(BrowserType.ANDROID, "", Platform.ANDROID);
45 | }
46 |
47 | public static DesiredCapabilities chrome() {
48 | LOG.info("Using `new ChromeOptions()` is preferred to `DesiredCapabilities.chrome()`");
49 | return new DesiredCapabilities(BrowserType.CHROME, "", Platform.ANY);
50 | }
51 |
52 | public static DesiredCapabilities firefox() {
53 | LOG.info("Using `new FirefoxOptions()` is preferred to `DesiredCapabilities.firefox()`");
54 | DesiredCapabilities capabilities = new DesiredCapabilities(
55 | BrowserType.FIREFOX,
56 | "",
57 | Platform.ANY);
58 | capabilities.setCapability("acceptInsecureCerts", true);
59 |
60 | return capabilities;
61 | }
62 |
63 | public static DesiredCapabilities htmlUnit() {
64 | return new DesiredCapabilities(BrowserType.HTMLUNIT, "", Platform.ANY);
65 | }
66 |
67 | public static DesiredCapabilities edge() {
68 | LOG.info("Using `new EdgeOptions()` is preferred to `DesiredCapabilities.edge()`");
69 | return new DesiredCapabilities(BrowserType.EDGE, "", Platform.WINDOWS);
70 | }
71 | public static DesiredCapabilities internetExplorer() {
72 | DesiredCapabilities capabilities = new DesiredCapabilities(
73 | BrowserType.IE, "", Platform.WINDOWS);
74 | capabilities.setCapability(CapabilityType.ForSeleniumServer.ENSURING_CLEAN_SESSION, true);
75 | return capabilities;
76 | }
77 |
78 | public static DesiredCapabilities operaBlink() {
79 | LOG.info("Using `new OperaOptions()` is preferred to `DesiredCapabilities.operaBlink()`");
80 | return new DesiredCapabilities(BrowserType.OPERA, "", Platform.ANY);
81 | }
82 |
83 | public static DesiredCapabilities safari() {
84 | LOG.info("Using `new SafariOptions()` is preferred to `DesiredCapabilities.safari()`");
85 | return new DesiredCapabilities(BrowserType.SAFARI, "", Platform.MAC);
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/it/SimpleRetestIdShowcaseIT.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.it;
2 |
3 | import java.nio.file.Path;
4 | import java.nio.file.Paths;
5 |
6 | import org.apache.commons.io.FileUtils;
7 | import org.assertj.core.api.Assertions;
8 | import org.junit.After;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.openqa.selenium.chrome.ChromeDriver;
12 | import org.openqa.selenium.chrome.ChromeOptions;
13 |
14 | import de.retest.recheck.Recheck;
15 | import de.retest.web.RecheckWebImpl;
16 | import de.retest.web.selenium.By;
17 | import de.retest.web.selenium.UnbreakableDriver;
18 |
19 | /*
20 | * Simple recheck-web showcase for a Chrome-based integration test. See other *IT classes for more examples.
21 | */
22 | public class SimpleRetestIdShowcaseIT {
23 |
24 | private UnbreakableDriver driver;
25 | private Recheck re;
26 |
27 | @Before
28 | public void setup() {
29 | // If ChromeDriver (http://chromedriver.chromium.org/downloads/) is not in your PATH, uncomment this and point to your installation.
30 | // System.setProperty( "webdriver.chrome.driver", "path/to/chromedriver" );
31 |
32 | final ChromeOptions opts = new ChromeOptions();
33 | opts.addArguments(
34 | // Enable headless mode for faster execution.
35 | "--headless",
36 | // Use Chrome in container-based Travis CI enviroment (see https://docs.travis-ci.com/user/chrome#Sandboxing).
37 | "--no-sandbox",
38 | // Fix window size for stable results.
39 | "--window-size=1200,800" );
40 | driver = new UnbreakableDriver( new ChromeDriver( opts ) );
41 |
42 | // Use the default implementation.
43 | re = new RecheckWebImpl();
44 | }
45 |
46 | @Test
47 | public void index() throws Exception {
48 | // Set the file name of the Golden Master.
49 | re.startTest( "simple-showcase" );
50 |
51 | // Do your Selenium stuff.
52 | final Path showcasePath = Paths.get( "src/test/resources/pages/showcase/retest-changed.html" );
53 | driver.get( showcasePath.toUri().toURL().toString() );
54 |
55 | // Single call instead of multiple assertions (doesn't fail on differences).
56 | re.check( driver, "index" );
57 |
58 | driver.findElement( By.retestId( "contact" ) ).click();
59 |
60 | // Works even if the GM is not yet created, if retestId is deterministic
61 | re.check( driver, "new_state" );
62 | driver.findElement( By.retestId( "for_testers" ) ).click();
63 |
64 | // Usually, we conclude the test case with re.capTest(),
65 | // but since we expect differences, we check these.
66 | Assertions.assertThatThrownBy( () -> re.capTest() ).hasMessageContainingAll( //
67 | "text: expected=\"Contact\", actual=\"Support\"", //
68 | "No Golden Master found. First time test was run?" );
69 | }
70 |
71 | @After
72 | public void tearDown() {
73 | driver.quit();
74 |
75 | // Produce the result file.
76 | // re.cap();
77 |
78 | // Not standard! Ensure we delete the GM that was created
79 | FileUtils.deleteQuietly( Paths.get( "src/test/resources/retest/recheck",
80 | "de.retest.web.it.SimpleRetestIdShowcaseIT", "simple-showcase.new_state.recheck" ).toFile() );
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/src/test/resources/pages/simple-page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello WebDriver
4 |
5 |
6 | Heading
7 |
8 | A single line of text
9 | A hidden line of text
10 |
11 |
12 |
A div containing
13 | More than one line of text
14 |
15 |
and block level elements
16 |
17 |
18 | An inline element
19 |
20 | This line has lots
21 |
22 | of spaces.
23 |
24 |
25 | This line has a non-breaking space
26 |
27 | This line has a non-breaking space and spaces
28 |
29 | These lines     have leading and trailing NBSPs
30 |
31 | This line has text within elements that are meant to be displayed
32 | inline
33 |
34 |
35 |
before pre
36 |
This section has a preformatted
37 | text block
38 | split in four lines
39 |
40 |
after pre
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
62 |
63 |
72 |
73 |
74 |
75 | Top level
76 |
79 |
80 |
81 |
82 | beforeSpace afterSpace
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | a link to an icon
91 |
92 | {a="b", c=1, d=true}
93 | {a="\\b\\\"'\'"}
94 |
95 | test
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/test/resources/pages/simple-page-diff.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Hello WebDriver
4 |
5 |
6 | Heading
7 | Subheading
8 | A single line of text
9 | A hidden line of text
10 |
11 |
12 |
A div containing
13 | More than one line of text
14 |
15 |
and block level elements
16 |
17 |
18 | An inline element
19 |
20 | This line has lots
21 |
22 | of spaces.
23 |
24 |
25 | This line has a non-breaking space
26 |
27 | This line has a non-breaking space and spaces
28 |
29 | These lines     have leading and trailing NBSPs
30 |
31 | This line has text within elements that are meant to be displayed
32 | inline
33 |
34 |
35 |
before pre
36 |
This section has a preformatted
37 | text block
38 | split in four lines
39 |
40 |
after pre
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
54 |
55 |
56 |
62 |
63 |
72 |
73 |
74 |
75 | Top level
76 |
79 |
80 |
81 |
82 | beforeSpace afterSpace
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | a link to an icon
91 |
92 | {a="b", c=1, d=true}
93 | {a="\\b\\\"'\'"}
94 |
95 | test
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/src/main/java/de/retest/web/selenium/By.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.function.Predicate;
6 |
7 | import de.retest.recheck.ui.descriptors.Element;
8 | import de.retest.recheck.ui.descriptors.RootElement;
9 | import de.retest.recheck.ui.diff.Alignment;
10 |
11 | public abstract class By extends org.openqa.selenium.By {
12 |
13 | public static ByBestMatchToRetestId retestId( final String retestId ) {
14 | return new ByBestMatchToRetestId( retestId );
15 | }
16 |
17 | public static Element findElement( final RootElement lastExpectedState, final RootElement lastActualState,
18 | final Predicate predicate ) {
19 | if ( lastExpectedState == null ) {
20 | throw new IllegalArgumentException( "Cannot find element in null state." );
21 | }
22 | final Element resultFromExpected = findElement( lastExpectedState.getContainedElements(), predicate );
23 | if ( resultFromExpected == null ) {
24 | return null;
25 | }
26 | final Alignment alignment = Alignment.createAlignment( lastExpectedState, lastActualState );
27 | final Element resultFromActual = alignment.getActual( resultFromExpected );
28 | if ( resultFromActual == null ) {
29 | throw new NoElementWithHighEnoughMatchFoundException( resultFromExpected );
30 | }
31 | return resultFromActual.applyRetestId( resultFromExpected.getRetestId() );
32 | }
33 |
34 | public static Element findElement( final List children, final Predicate predicate ) {
35 | for ( final Element element : children ) {
36 | if ( predicate.test( element ) ) {
37 | return element;
38 | }
39 | final Element result = findElement( element.getContainedElements(), predicate );
40 | if ( result != null ) {
41 | return result;
42 | }
43 | }
44 | return null;
45 | }
46 |
47 | public static List findElements( final List children, final Predicate predicate ) {
48 | final List result = new ArrayList<>();
49 | for ( final Element element : children ) {
50 | if ( predicate.test( element ) ) {
51 | result.add( element );
52 | }
53 | result.addAll( findElements( element.getContainedElements(), predicate ) );
54 | }
55 | return result;
56 | }
57 |
58 | public static Element findElementByAttribute( final RootElement lastExpectedState,
59 | final RootElement lastActualState, final String attributeName, final Predicate condition ) {
60 | return findElement( lastExpectedState, lastActualState, element -> {
61 | if ( element.getIdentifyingAttributes().get( attributeName ) != null ) {
62 | return condition.test( element.getIdentifyingAttributes().getAttribute( attributeName ).getValue() );
63 | } else if ( element.getAttributes().get( attributeName ) != null ) {
64 | return condition.test( element.getAttributes().get( attributeName ) );
65 | } else {
66 | return false;
67 | }
68 | } );
69 | }
70 |
71 | public static Element findElementByAttribute( final RootElement lastExpectedState,
72 | final RootElement lastActualState, final String attributeName, final Object attributeValue ) {
73 | return findElementByAttribute( lastExpectedState, lastActualState, attributeName, attributeValue::equals );
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/resources/de/retest/web/it/TestHealerCssSelectorIT.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | retest - Java Swing GUI Testing
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
23 |
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Unvisited
40 |
41 | Visited
42 |
43 | Target
44 |
45 | other
47 | default
49 |
50 |
51 |
52 |
57 |
58 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/selenium/WriteToResultWarningConsumerTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web.selenium;
2 |
3 | import static org.mockito.Mockito.mock;
4 | import static org.mockito.Mockito.never;
5 | import static org.mockito.Mockito.verify;
6 | import static org.mockito.Mockito.when;
7 |
8 | import java.util.Collections;
9 | import java.util.stream.Stream;
10 |
11 | import org.junit.jupiter.api.BeforeEach;
12 | import org.junit.jupiter.api.Test;
13 |
14 | import de.retest.recheck.ui.diff.AttributeDifference;
15 | import de.retest.recheck.ui.diff.ElementDifference;
16 | import de.retest.recheck.ui.diff.ElementIdentificationWarning;
17 | import de.retest.web.selenium.WriteToResultWarningConsumer.ElementDifferenceRetriever;
18 |
19 | class WriteToResultWarningConsumerTest {
20 |
21 | ElementDifferenceRetriever retriever;
22 | WriteToResultWarningConsumer cut;
23 |
24 | ElementIdentificationWarning warning;
25 | QualifiedElementWarning qualifiedWarning;
26 |
27 | @BeforeEach
28 | void setUp() {
29 | retriever = mock( ElementDifferenceRetriever.class );
30 |
31 | cut = new WriteToResultWarningConsumer( retriever );
32 |
33 | warning = new ElementIdentificationWarning( "file.java", 0, "id", "File" );
34 | qualifiedWarning = new QualifiedElementWarning( "id", "id", warning );
35 | }
36 |
37 | @Test
38 | void accept_should_find_warning_and_add_to_result() throws Exception {
39 | final AttributeDifference attribute = mock( AttributeDifference.class );
40 | when( attribute.getKey() ).thenReturn( "id" );
41 |
42 | final ElementDifference difference = mock( ElementDifference.class );
43 | when( difference.getRetestId() ).thenReturn( "id" );
44 | when( difference.getAttributeDifferences() ).thenReturn( Collections.singletonList( attribute ) );
45 |
46 | when( retriever.getDifferences() ).thenReturn( Stream.of( difference ) );
47 |
48 | cut.accept( qualifiedWarning );
49 |
50 | verify( attribute ).addElementIdentificationWarning( warning );
51 | }
52 |
53 | @Test
54 | void accept_should_not_add_if_attribute_does_not_match() throws Exception {
55 | final AttributeDifference attribute = mock( AttributeDifference.class );
56 | when( attribute.getKey() ).thenReturn( "foo" );
57 |
58 | final ElementDifference difference = mock( ElementDifference.class );
59 | when( difference.getRetestId() ).thenReturn( "id" );
60 | when( difference.getAttributeDifferences() ).thenReturn( Collections.singletonList( attribute ) );
61 |
62 | when( retriever.getDifferences() ).thenReturn( Stream.of( difference ) );
63 |
64 | cut.accept( qualifiedWarning );
65 |
66 | verify( attribute, never() ).addElementIdentificationWarning( warning );
67 | }
68 |
69 | @Test
70 | void accept_should_not_add_if_retest_id_does_not_match() throws Exception {
71 | final AttributeDifference attribute = mock( AttributeDifference.class );
72 | when( attribute.getKey() ).thenReturn( "id" );
73 |
74 | final ElementDifference difference = mock( ElementDifference.class );
75 | when( difference.getRetestId() ).thenReturn( "foo" );
76 | when( difference.getAttributeDifferences() ).thenReturn( Collections.singletonList( attribute ) );
77 |
78 | when( retriever.getDifferences() ).thenReturn( Stream.of( difference ) );
79 |
80 | cut.accept( qualifiedWarning );
81 |
82 | verify( attribute, never() ).addElementIdentificationWarning( warning );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/resources/retest/recheck/de.retest.web.it.RecheckRemoteWebElementIT/empty-article-FirefoxDriver.open.recheck/retest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 8
9 | 900
10 | 0
11 | 1340
12 |
13 |
14 | 0
15 | 892
16 | -892
17 | 0
18 |
19 | html[1]/body[1]/article[1]
20 | 1
21 | article
22 |
23 |
24 |
25 |
26 |
27 | shown
28 | true
29 |
30 |
31 |
32 |
33 |
34 |
35 | driver.type
36 | FirefoxDriver
37 |
38 |
39 | time.offset
40 | +01:00
41 |
42 |
43 | os.arch
44 | amd64
45 |
46 |
47 | check.type
48 | element
49 |
50 |
51 | vcs.commit
52 | 6494eed61cc748f758146dbd619b7e05660caa65
53 |
54 |
55 | window.height
56 | 768
57 |
58 |
59 | vcs.branch
60 | feature/check-clickability
61 |
62 |
63 | url
64 | file:///home/travis/build/retest/recheck-web/src/test/resources/pages/form-page.html
65 |
66 |
67 | os.version
68 | 5.3.0-40-generic
69 |
70 |
71 | browser.name
72 | firefox
73 |
74 |
75 | browser.version
76 | 73.0.1
77 |
78 |
79 | time.time
80 | 09:04:34.28
81 |
82 |
83 | time.zone
84 | Europe/Berlin
85 |
86 |
87 | window.width
88 | 1366
89 |
90 |
91 | os.name
92 | LINUX
93 |
94 |
95 | time.date
96 | 2020-03-17
97 |
98 |
99 | vcs.name
100 | git
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/src/test/java/de/retest/web/PeerConverterTest.java:
--------------------------------------------------------------------------------
1 | package de.retest.web;
2 |
3 | import static java.util.Arrays.asList;
4 | import static java.util.Collections.singletonList;
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | import org.junit.jupiter.api.Test;
13 |
14 | import de.retest.recheck.ui.DefaultValueFinder;
15 | import de.retest.recheck.ui.descriptors.RootElement;
16 | import de.retest.recheck.ui.diff.RootElementDifference;
17 | import de.retest.recheck.ui.diff.RootElementDifferenceFinder;
18 | import de.retest.recheck.util.RetestIdProviderUtil;
19 | import de.retest.web.mapping.PathsToWebDataMapping;
20 |
21 | class PeerConverterTest {
22 |
23 | @Test
24 | void getParentPath_should_return_null_for_toplevel() {
25 | assertThat( PeerConverter.getParentPath( "//HTML[1]" ) ).isNull();
26 | }
27 |
28 | @Test
29 | void getParentPath_should_return_parent_path() {
30 | assertThat( PeerConverter.getParentPath( "//HTML[1]/BODY[1]" ) ).isEqualTo( "//HTML[1]" );
31 | }
32 |
33 | @Test
34 | void convertToPeers_should_result_in_valid_tree() throws Exception {
35 | final List> data = new ArrayList<>();
36 | data.add( asList( "//HTML[1]/BODY[1]", toHashMap( "BODY" ) ) );
37 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]", toHashMap( "DIV" ) ) );
38 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]", toHashMap( "DIV" ) ) );
39 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]/LI[1]", toHashMap( "LI" ) ) );
40 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]/LI[1]/A[1]", toHashMap( "A" ) ) );
41 | data.add( asList( "//HTML[1]", toHashMap( "HTML" ) ) );
42 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]", toHashMap( "FOOTER" ) ) );
43 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]/LI[2]", toHashMap( "LI" ) ) );
44 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]/LI[2]/A[1]", toHashMap( "A" ) ) );
45 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]/LI[3]", toHashMap( "LI" ) ) );
46 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]", toHashMap( "UL" ) ) );
47 | data.add( asList( "//HTML[1]/BODY[1]/FOOTER[1]/DIV[1]/DIV[1]/UL[1]/LI[3]/A[1]", toHashMap( "A" ) ) );
48 | final PathsToWebDataMapping mapping = new PathsToWebDataMapping( data );
49 |
50 | final DefaultValueFinder defaultValueFinder = ( identifyingAttributes, attributeKey, attributeValue ) -> false;
51 | final PeerConverter cut = new PeerConverter( RetestIdProviderUtil.getConfiguredRetestIdProvider(), mapping,
52 | "title", null, defaultValueFinder, null );
53 | final RootElement root = cut.convertToPeers();
54 |
55 | final RootElementDifferenceFinder diffFinder = new RootElementDifferenceFinder( defaultValueFinder );
56 | final List diffs =
57 | diffFinder.findDifferences( singletonList( root ), singletonList( root ) );
58 | assertThat( diffs ).isEmpty();
59 | }
60 |
61 | private Map toHashMap( final String tagName ) {
62 | final Map result = new HashMap<>();
63 | result.put( "tagName", tagName );
64 | result.put( "x", "10" );
65 | result.put( "y", "10" );
66 | result.put( "height", "100" );
67 | result.put( "width", "100" );
68 | return result;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------