From c18b3394b03b31d4f61d3bb3ee4cfd705c2e3b41 Mon Sep 17 00:00:00 2001 From: Adrian Hawkins-Longley Date: Tue, 2 Jan 2024 16:25:35 +0000 Subject: [PATCH] Playwright driver (#231) * Add playwright driver to project, implement visit * PlaywrightElement - 117/194 Driver tests passing * PlaywrightWindow - 133/194 Driver tests * Set fields. 147/194 driver tests * Typing in fields * Windows 166/194 DriverSpecs * Frames 181/191 driver specs * Improve headless section of readme * Implement dialog handling wrappers as per Capy+PW * Fix a few test on pw+firefox * Fix readme link --- History.md | 2012 +++++++++-------- README.md | 318 ++- .../Examples/ApiExamples.cs | 317 +-- .../Examples/ClickExamples.cs | 25 +- ...ion.cs => CustomSeleniumBrowserSession.cs} | 10 +- .../Examples/ModalDialogExamples.cs | 22 +- .../Examples/SelectFromExamples.cs | 21 +- .../Examples/WindowExamples.cs | 24 +- src/Coypu.AcceptanceTests/Location.cs | 153 +- .../StaleScopeExamples.cs | 351 +-- .../TextPrecisionAndMatch.cs | 212 +- .../WaitAndRetryExamples.cs | 129 +- src/Coypu.AcceptanceTests/WebRequests.cs | 150 +- .../sites/SelfHostedSite.cs | 6 +- src/Coypu.Drivers.Tests/DriverSpecs.cs | 13 +- .../When_accessing_attributes.cs | 37 +- .../When_clearing_cookies.cs | 84 - .../When_executing_script.cs | 71 +- .../When_finding_fieldsets.cs | 58 +- .../When_finding_iframes.cs | 58 +- .../When_finding_sections.cs | 102 +- .../When_finding_sections_as_divs.cs | 104 +- .../When_finding_windows.cs | 270 +-- .../When_inspecting_dialog_text.cs | 65 +- .../When_inspecting_location.cs | 88 +- .../When_interacting_with_dialogs.cs | 132 -- .../When_refreshing_windows.cs | 18 +- .../When_selecting_options.cs | 111 +- .../When_setting_fields.cs | 228 +- .../When_sizing_windows.cs | 192 +- .../html/InteractionTestsPage.htm | 899 ++++---- src/Coypu.Drivers.Tests/html/iFrame1.htm | 1 + src/Coypu.Drivers.Tests/html/iFrame2.htm | 1 + .../FindsFirstWitNoDisambiguationStrategy.cs | 65 +- src/Coypu.Tests/TestDoubles/FakeDriver.cs | 828 ++++--- src/Coypu.Tests/TestDoubles/StubDriver.cs | 357 +-- .../BrowserInteractionTests.cs | 300 +-- .../When_completing_forms.cs | 316 +-- .../When_interacting_with_modal_dialogs.cs | 61 +- src/Coypu/Actions/Select.cs | 138 +- src/Coypu/ActivatorDriverFactory.cs | 48 +- src/Coypu/AsyncExtensions.cs | 31 + src/Coypu/BrowserSession.cs | 344 +-- src/Coypu/BrowserWindow.cs | 428 ++-- src/Coypu/Cookies.cs | 128 +- src/Coypu/Coypu.csproj | 8 +- src/Coypu/DriverFactory.cs | 18 +- src/Coypu/DriverScope.cs | 774 +++---- src/Coypu/Drivers/Browser.cs | 99 +- src/Coypu/Drivers/Playwright/Cookies.cs | 105 + src/Coypu/Drivers/Playwright/Dialogs.cs | 45 + src/Coypu/Drivers/Playwright/FrameFinder.cs | 53 + .../Drivers/Playwright/PlaywrightDriver.cs | 407 ++++ .../Drivers/Playwright/PlaywrightElement.cs | 67 + .../Drivers/Playwright/PlaywrightFrame.cs | 62 + .../Drivers/Playwright/PlaywrightWindow.cs | 47 + src/Coypu/Drivers/Selenium/Cookies.cs | 140 ++ src/Coypu/Drivers/Selenium/Dialogs.cs | 131 +- src/Coypu/Drivers/Selenium/DriverFactory.cs | 93 +- src/Coypu/Drivers/Selenium/ElementFinder.cs | 90 +- src/Coypu/Drivers/Selenium/SeleniumFrame.cs | 80 +- .../Drivers/Selenium/SeleniumWebDriver.cs | 704 +++--- .../Drivers/Selenium/SeleniumWindowManager.cs | 112 +- .../Drivers/Selenium/WindowHandleFinder.cs | 166 +- src/Coypu/Finders/DocumentElementFinder.cs | 44 +- .../FinderOptionsDisambiguationStrategy.cs | 78 +- src/Coypu/Finders/FrameFinder.cs | 36 +- src/Coypu/Finders/OptionFinder.cs | 38 +- src/Coypu/Finders/XPathFinder.cs | 126 +- src/Coypu/IDriver.cs | 15 +- src/Coypu/Queries/ElementMissingQuery.cs | 68 +- src/Coypu/Queries/HasDialogQuery.cs | 40 +- src/Coypu/SessionConfiguration.cs | 159 +- src/Coypu/SnapshotElementScope.cs | 83 +- .../Timing/RetryUntilTimeoutTimingStrategy.cs | 258 ++- .../WebRequests/WebRequestCookieInjector.cs | 2 +- 76 files changed, 7298 insertions(+), 6176 deletions(-) rename src/Coypu.AcceptanceTests/Examples/{CustomBrowserSession.cs => CustomSeleniumBrowserSession.cs} (86%) delete mode 100644 src/Coypu.Drivers.Tests/When_clearing_cookies.cs delete mode 100644 src/Coypu.Drivers.Tests/When_interacting_with_dialogs.cs create mode 100644 src/Coypu/AsyncExtensions.cs create mode 100644 src/Coypu/Drivers/Playwright/Cookies.cs create mode 100644 src/Coypu/Drivers/Playwright/Dialogs.cs create mode 100644 src/Coypu/Drivers/Playwright/FrameFinder.cs create mode 100644 src/Coypu/Drivers/Playwright/PlaywrightDriver.cs create mode 100644 src/Coypu/Drivers/Playwright/PlaywrightElement.cs create mode 100644 src/Coypu/Drivers/Playwright/PlaywrightFrame.cs create mode 100644 src/Coypu/Drivers/Playwright/PlaywrightWindow.cs create mode 100644 src/Coypu/Drivers/Selenium/Cookies.cs diff --git a/History.md b/History.md index bd977b14..30804a56 100644 --- a/History.md +++ b/History.md @@ -1,1004 +1,1008 @@ -# Coypu 3.1.1 - 19.07.2019 -* [#203](https://github.com/featurist/coypu/issues/203) fix for saving file as png saves the contents as jpg for .Net Core - Thanks to @basimc4 for PR - -Can be also downloaded as nuget packages from https://www.nuget.org/packages/Coypu/ - -# Coypu 3.1.0 - 17.07.2019 -* [#205](https://github.com/featurist/coypu/issues/205) Firefox fails to switch out of iframe - Thanks to @Hawxy for PR -* plus small changes in tests - -Can be also downloaded as nuget packages from https://www.nuget.org/packages/Coypu/ - -# Coypu 3.0.1 - 13.12.2018 -* [#199](https://github.com/featurist/coypu/issues/199) removing nuspec related files to allign with new approach of creating nuget packages from csproj files - -Can be also downloaded as nuget packages from https://www.nuget.org/packages/Coypu/ - -## Coypu.NUnit 3.0.1 - 13.12.2018 -* nuspec still used to generate nuget package - -Can be also downloaded as nuget package from https://www.nuget.org/packages/Coypu.NUnit/ - -# Coypu 3.0.0-rc - 11.12.2018 -Can be also downloaded as nuget package from: https://www.nuget.org/packages/Coypu/ -### Breaking changes -* support for .NET Framework 4.5+ -* support for .NET Standard 2.0+ -* [#144](https://github.com/featurist/coypu/issues/144) support for .NET Core 2.0+ -* updated Selenium WebDriver to 3.141.0 -* removed all projects and dependencies related to WatiN -* removed methods from DriverScope and IScope which has been marked as obsolete since 2014: - - HasCss() - - HasNoCss() - - HasXPath() - - HasNoXPath() -### Fixes -* [#92](https://github.com/featurist/coypu/issues/92) Added the ability to pass in separate options to the `Select("value", selectOptions).From("selectId", fromOptions)` methods -* [#189](https://github.com/featurist/coypu/issues/189) fixed by update of selenium webdriver to v3.141.0 -* [#146](https://github.com/featurist/coypu/issues/146) and [#164](https://github.com/featurist/coypu/issues/164) fix for WaitBeforeClick for general Click(), previously option WaitBeforeClick worked only for ClickButton() and ClickLink() methods -* various fixes for failing unit, driver and acceptance tests -### Changes -* added new Cookies class with methods which are available from `_browserSession.Driver.Cookies`: - - AddCookie(cookie) - - DeleteAll() - - DeleteCookie(cookie) - - DeleteCookieNamed(cookieName) - - GetCookieNamed(cookieName) - - WaitUntilCookieExists(cookie, options) -* methods marked as obsolete: - - GetBrowserCookies() -> use instead `_browserSession.Driver.Cookies.GetAll()` - - ClearBrowserCookies() -> use instead `_browserSession.Driver.Cookies.DeleteAll()` -* migrate solution projects to new VS2017 format -* updated browser drivers to newest versions (geckodriver, chromedriver, iedriver) - -## Coypu.NUnit 3.0.0-rc - 11.12.2018 -### Breaking changes -* support for .NET Framework 4.5+ -* support for .NET Standard 2.0+ -* [#144](https://github.com/featurist/coypu/issues/144) support for .NET Core 2.0+ -* updated Coypu to v3.0.0-rc - -# Version 2.11.0 - -### Updated - -* Update min Selenium dependency to 3.0.1 - -Release date: 2016-12-06 - - -# Version 2.10.0 - -### Updated - -* Update min Selenium dependency to 2.48.0 - -Release date: 2015-10-23 - - -# Version 2.9.1 - -### Added - -* Fix FindXPath with text to use xpath builder rather than slow post filter - -Release date: 2015-05-29 - - -# Version 2.9.0 - -### Added - -* FindXPath takes a text parameter like FindCss - -Release date: 2015-05-29 - - -# Version 2.8.0 - -### Added - -*Added possibility to pass arguments to executeScript #128 (@drauch) - -Release date: 2015-01-26 - - -# Version 2.7.0 - -### Added (incl Coypu.WatiN 2.2.0) - -* ElementScope gets Disabled property - Closes #123 - -Release date: 2014-12-12 - - -# Version 2.6.1 - -### Fixed - -* Fixes #121 - stale element ref exception calling FindAllCss/XPath on stale outer scope - -Release date: 2014-12-03 - - -# Version 2.6.0 - -### Fixed - -* Handle stale elements/windows by exceptions rather than explicit stale checks - fixes #112 AcceptModalDialog() fails in IE11 -* Override timeout in Exists() and Missing() -* NoAlertPresentException when Disposing Chrome Browser -* ElementScope's properties (HTML attribute access) race condition - fixes #117 - -Release date: 2014-11-30 - -# Version 2.5.1 - -### Fixed - -* Fix AppHost to accept basic auth userinfo - -# Version 2.5.0 - -### Added - -* Added CLSCompliant(true) to all production assemblies. (@drauch) - -### Fixed - -* Firefox webdriver 2.43.0 has started closing alerts when asked for currentWindowHandle - -### Changed - -* Thanks to fix for Firefox Webdriver, if you switch window via the native selenium driver -coypu will no longer be able to switch back to the correct window if you use a pre-existing scope -so use the native SwitchTo with caution. - -# Version 2.4.1 - -### Fixed - -* Only switch to window scope when necessary -* Support more html5 input types, fixes #104 - -Release date: 2014-07-25 - - -# Version 2.4.0 - -### Added - -* Parameterized BrowserSession to take in a Driver (@Br3ttl3y) - -Release date: 2014-07-25 - - -# Version 2.3.0 - -### Added - -* Upgrade to WebDriver 2.41 -* Android dri ver removed from WebDriver 2.41 - -Release date: 2014-04-01 - - -# Version 2.2.0 - -### Added - -* Scope.FindIdEndingWith() for Asp.Net Web Forms -* ElementScope.SelectOption() for finding a select then picking an option later - -Release date: 2014-03-18 - - -# Version 2.1.4 - -### Fixed - -* Fixed issue where Options.ToString() was causing a stack overflow fixes #85 (@adexios) - -Release date: 2014-02-12 - - -# Version 2.1.3 - -### Fixed - -* Fix null reference in queries with snapshot scopes (FindALL) fixes #84 - -Release date: 2014-02-04 - - -# Version 2.1.2 - -### Fixed - -* Find select and options seperately to allow differing TextPrecisions when PreferExact - -Release date: 2014-01-31 - - -# Version 2.1.1 - -### Fixed - -* Exact text not matching selects found by container labels - -Release date: 2014-01-31 - - -# Version 2.1.0 - -### Changed - -* NUnit matchers split out into seperate package Coypu.NUnit and Coypu.NUnit262 for those that really need NUnit 2.6.2 - -Release date: 2014-01-30 - - -# Version 2.0.1 - -### Added - -* New Exact and Match options (http://www.featurist.co.uk/blog/2014/01/22/coypu-2-0/) - -### Changed - -* Exact = false by default - all finders match partial text by default -* Match.Single by default - more than one matching element throws AmbiguousException -* Depend on same major version of dependencies - -### Fixed - -* On individual calls, only specified options override session configuration - -### Removed - -* HasCss, HasXPath etc - Just use .Exists()/.Missing() on any scope or Assert.That(scope, Shows.Css(".summat")) - -Release date: 2014-01-02 - -# Version 1.0.0 - -### Updated - -* Selenium WebDriver 2.39.0 - -Release date: 2013-12-20 - - -# Version 0.23.1 - -### Fix - -* Stack overflow in Shows.CssContaining :o - -Release date: 2013-11-21 - - -# Version 0.23.0 - -### Added - -* History: BrowserWindow.GoBack() and BrowserWindow.GoForward() - -Release date: 2013-11-18 - - -# Version 0.22.2 - -### Fixed - -* Actual values in matcher feedback could be wrong -* Fix for text of an iframe - -Release date: 2013-11-08 - - -# Version 0.22.1 - -### Fixed - -* Radio buttons should NOT be found by name - -Release date: 2013-11-06 - - -### Added - -* Support for url input type (@ajh79) -* Shows.Value; Shows.No.Value; HasValue; HasNoValue on ElementScope (@kberridge) -* BrowserWindow.Refresh() (@stefanforsberg) - -### Updated - -* Latest NUnit - -Release date: 2013-11-06 - - -# Version 0.21.1 - -### Fixed - -* Correct dependency on Selenium WebDriver 2.37.0 - -Release date: 2013-10-29 - -# Version 0.21.0 - -Release date: 2013-10-28 - -### Added - -* InnerHTML and OuterHTML properties -* NotSupportedException finding invisible elements by text in WebDriver - -### Updated - -* Selenium WebDriver 2.37.0 - -### Fixed - -* Null reference exception when waiting for invisible elements to appear - -# Version 0.20.1 - -Release date: 2013-10-18 - -### Fixed - -* fixes #70 Inputs with no type attribute can now be found by name/id/placeholder - -# Version 0.20.1 - -Release date: 2013-10-18 - -### Added - -* Shows.AllCssInOrder and ContainsCss matchers for assertions against multiple css matches - -# Version 0.19.3 - -Release date: 2013-09-26 - -### Updated - -* Performance improvements for WebDriver - -# Version 0.19.2 - -Release date: 2013-09-25 - -### Fixed - -* Find state (no options) was not respecting session configuration - - -# Version 0.19.1 - -Release date: 2013-09-10 - -### Updated - -* Selenium WebDriver 2.35.0 - - -# Version 0.19.0 - -Release date: 2013-07-31 - -### Added - -* Windows found by title contains if no exact match - -### Updated - -* Selenium WebDriver 2.33.0 -* Refactoring to single xpaths for performance & standardisation across drivers - -### Removed - -* Fields and buttons are no longer found by ID ends with, only an exact match of ID - - -# Version 0.18.0 - -Release date: 2013-06-18 - -### Added - -* SaveScreenshot(fileName) - - -# Version 0.17.0 - -Release date: 2013-06-17 - -### Added - -* MaximiseWindow - -#### Fixed - -* Visit now operates on the correct window in all browsers -* WatiNDriver gets a bunch of fixes for window scope (ExecuteScript, Dialogs, Visit) -* WatiNDriver now supports Title and Location -* WatinDriver now passes the "tricky" window tests i.e. window references survive popups being closed and reopened just like the selenium driver does. - - -# Version 0.16.3 - -Release date: 2013-06-04 - -### Removed - -* No longer a dependency on Newtonsoft.Json - - -# Version 0.16.2 - -Release date: 2013-05-07 - -### Updated - -* Selenium.WebDriver 2.32.1 -* WebDrver selects now use Selenium.Support -* iframes found by css/xpath/id can be used as scopes (WebDriver only) - - -# Version 0.16.1 - -Release date: 2013-04-22 - -### Fixed - -* FindCss with exact text now escapes regex chars correctly - - -# Version 0.16.0 - -Release date: 2013-04-19 - -### Added - -* FindCss can now take required text for it to match an element -* HasCss and HasNoCss use get text filter via FindCss, and matchers - - -# Version 0.15.2 - -Release date: 2013-04-18 - -### Fixed - -* One more place left to clean up forceAllEvents - - -# Version 0.15.1 - -Release date: 2013-04-18 - -### Fixed - -* Left forceAllEvents hanging in the API by mistake - -# Version 0.15.0 - -Release date: 2013-04-18 - -### Added - -* FindAllCss and FindAllXPath now take a predicate which will be retried - -### Removed - -* Remove default fill in via Javascript. Native key events always fire now. - - -# Version 0.14.0 - -Release date: 2013-03-20 - -### Added - -* Added FillInWith/Check/Uncheck to ElementScope (@refractalize) - -### Removed - -* BrowserSession.FillIn(ElementScope) (@refractalize) - - -# Version 0.13.0 - -Release date: 2013-03-07 - -### Added - -* WebDriver now mostly supports PhantomJS so added to Coypu Browsers. Tests for iframes and modal dialogs fail in SeleniumWebdriver+PhantomJS - -# Version 0.12.8 - -Release date: 2012-03-07 - -### Updated - -* Configuration.AppHost may contain http:// or https:// - -# Version 0.12.7 - -Release date: 2013-03-06 - -### Fixed - -* WebDriver dependency is now correct: 2.31.1 - -# Version 0.12.6 - -Release date: 2013-03-04 - -### Updated - -* WebDriver 2.31.1 - -# Version 0.12.5 - -Release date: 2013-02-22 - -# Added Shows.Content and Shows.No.Content NUnit matchers - -# Version 0.12.4 - -Release date: 2013-02-15 - -### Updated - -* WebDriver 2.29.1 - -# Version 0.12.3 - -Release date: 2012-12-12 - -### Updated - -* WebDriver 2.28 - -# Version 0.12.2 - -Release date: 2012-12-04 - -### Fixed - -* Fixes #37 WATIN respect forceAllEvents (@AidenMontgomery) -* Fixes #36 Allow interaction with input fields of type tel (@AidenMontgomery) - -# Version 0.12.1 - -Release date: 2012-11-28 - -### Updated - -* Any element with class 'btn' is considered a button to match bootstrap convention - -# Version 0.12.0 - -Release date: 2012-11-13 - -### Fixed - -* FillIn now works when passed and ElementScope - -### Added - -* SendKeys added to ElementFound - -# Version 0.11.1 - -Release date: 2012-11-13 - -### Updated - -* FillIn only clicks on element first when forceAllEvents is set - -# Version 0.11.0 - -Release date: 2012-11-07 - -### Updated - -* FindAll API now returns a list of snapshot element scopes - -# Version 0.10.3 - -Release date: 2012-11-07 - -### Updated - -* WebDriver 2.26.0 - -### Fixed - -* Dialogs scoped correctly to windows - - Fixes non user-triggered dialogs accessed directly after interacting with a different window - -# Version 0.10.2 - -### Updated - -* WebDriver 2.25.1 -* When a window is missing a MissingWindowException is thrown, rather than MissingHtmlException - -# Version 0.10.0 - -Release date: 2012-06-20 - -### Updated - -* WebDriver 2.23 -* Elements are only found by partial ID if there is a leading underscore - - This was only intended for asp.net webforms testing and was causing too many collisions -* Removed all Obsolete methods left over from < 0.8.0 - -# Version 0.8.10 - -Release date: 2012-05-01 - -### Added - -* Expose window title - -# Version 0.8.9 - -Release date: 2012-04-24 - -### Fixed - -* Dependency on WebDriver 2.21 broken - -# Version 0.8.8 - -Release date: 2012-04-23 - -### Added - -* Browser.Parse() helper for dynamic configuration of browser - -# Version 0.8.7 - -Release date: 2012-04-23 - -### Added - -* FindFrame finds both iframes and old school frameset frames, FindIframe is now obsolete. - -### Fixed - -* Location respects window scope in all cases - -# Version 0.8.6 - -Release date: 2012-04-18 - -### Updated - -* WebDrier 2.21 - Fixes hover - -# Version 0.8.5 - -Release date: 2012-04-12 - -### Fixed - -* Coypu.xml not being packaged - -# Version 0.8.4 - -Release date: 2012-04-12 - -### Fixed - -* Finds fields with type=email - -# Version 0.8.3 - -Release date: 2012-04-11 - -### Fixed - -* Only yse JS to fillin in js-supporting drivers, i.e. not HtmlUnit - -# Version 0.8.2 - -Release date: 2012-04-05 - -### Fixed - -* FillIn now uses JS by default for speed in WebDriver. - -# Version 0.8.1 - -Release date: 2012-04-05 - -### Updated - -* FillIn now uses JS by default for speed in WebDriver. - -### Fixed - -* Fill in making unneccassary driver calls. - -# Version 0.8.0 - -Release date: 2012-04-03 - -** NOTE: Breaking changes ** - -Check the README for the latest API - -### Removed -* Static configuration and static Browser.Session - use new BrowserSession(Configuration) and manage lifetime yourself. - -### Updated -* Find methods return ElementScope with deferred execution. This replaces the browser.Within() functionality. -* Timing Options can be passed to every method to override configuration. This replaces browser.WithIndividualTimeout() - -### Added -* Nested scopes -* Windows as scopes - -# Version 0.7.3 - -Release date: 2012-03-20 - -### Fixed -* Quit not Close + Dispose fixes HtmlUnit & Chrome not closing properly - -# Version 0.7.2 - -Release date: 2012-02-27 - -### Updated -* Minimum Selenium.WebDriver is now 2.19.0 - -### Added to Coypu.WatiN 0.6.0 -* Watin will now consider invisible elements (citizenmatt) -* Watin can now find by css (citizenmatt) -* Watin handles FindField and partial ID correctly (citizenmatt) -* Watin - Only buttons and fields now match by partial id (citizenmatt) -* Watin frames can now be used as scopes (citizenmatt) -* Watin - Find frames support (citizenmatt) -* Watin - Options in a select list are now selected by text or value (citizenmatt) -* WatiNDriver now supports cookies (citizenmatt) -* WatiNDriver now supports hover (citizenmatt) -* Added support for Accept/CancelModalDialog to WatiNDriver (citizenmatt) -* Added HasDialog support to WatiNDriver (citizenmatt) - -### Changed -* Selenium 2.11.0 or later - -# Version 0.7.0 - -Release date: 2011-02-01 - -### Added -* Simple support for multiple sessions - -# Version 0.6.0 - -Release date: 2011-11-19 - -### Added -* ConsideringInvisibleElements scope added to sessions - for when you reeeeeally need it - -# Version 0.5.4 - -Release date: 2011-11-10 - -### Fixed -* Session.Location not respecting iframe scope - -# Version 0.5.3 - -Release date: 2011-11-06 - -### Changed -* Selenium 2.11.0 or later - -# Version 0.5.2 - -Release date: 2011-10-18 - -### Fixed -* Proj dependencies broken - -# Version 0.5.1 - -Release date: 2011-10-18 - -### Fixed -* Dependency on Newtonsoft.Json via Selenium.WebDriver broken -* You may need to Uninstall-Package for Coypu, Selenium.WebDriver and Newtonsoft.Json then Install-Package Coypu - -# Version 0.5.0 - -Release date: 2011-10-11 - -### Added -* HtmlUnit help - -### Changed -* Upgrade to Selenium.WebDriver 2.7.0 - -### BREAKING CHANGE -* Timeout default down from 10 to 1 sec - -# Version 0.4.0 - -Release date: 2011-09-02 - -### Added -* Expose browser's current location - -### Changed -* Split out WatiN driver into seperate Coypu.Watin package, only WebDriver is included in Coypu by default. -* Reference official Selenium on nuget and make a dependency of Coypu.nuget - -### Fixed -* Error on second use of IFrame scope within the same block - -# Version 0.3.0 - -Release date: 2011-08-02 - -### Fixed -* FindSection not working across all versions of Firefox - -### Changed -* Will bump the minor version for any API/behaviour changes from now on as I should have been doing - bump to 0.3.0 to break with 0.2.x - -# Version 0.2.10 - -Release date: 2011-07-25 - -### Fixed -* FindSection patched to remove compound css selector after this broke in WebDriver 2.1 - -### Added -* Overload for Within that returns a result from within the given scope - -# Version 0.2.9 - -Release date: 2011-07-21 - -### Changed -* Update to Selenium WebDriver 2.1.0 - -# Version 0.2.8 - -Release date: 2011-07-18 - -### Added -* SaveWebResource saves a resource from the web directly using the current browser session's cookies -* Find buttons by role='button' attr - -### Fixed -* Escaping both types of quotes in xpath literals - -# Version 0.2.7 - -Release date: 2011-07-12 - -### Added -* FindState - finds the first to be reached from a list of possible states the page may be in - -### Fixed -* Don't click in file fields, was bringing up the browse dialog in some browsers - -# Version 0.2.6 - -Release date: 2011-07-04 - -### Changed -* Selenium WedDriver RC3 is now on nuget, so it's a dependency again. - -### Added -* Enabled AndroidDriver for Selenium -* SeleniumWebDriver now exposes a constructor overload that takes in an instance of RemoteWebDriver. This allows you to create your own derived drivers that can make use of the RemoteWebDriver (e.g. using HtmlUnitDriver) (citizenmatt) - -# Version 0.2.5 - -Release date: 2011-07-03 - -### Fixed -* Fix issue #13 - HasNo methods are knackered - -# Version 0.2.4 - -Release date: 2011-06-30 - -### Added -* Adding doc comments to Session API - -### Changed -* Bunch of stuff that should have been internal - -# Version 0.2.3 - -Release date: 2011-06-27 - -### Changed -* Upgrade of Selenium WebDriver to 2.0rc3 - -# Version 0.2.2 - -Release date: 2011-06-27 - -### Added -* Rounded out the session API with FindSection, FindFieldset and FindId and make the Session.Driver internal. -* Has(Func) and HasNot(Func) for custom queries. -* Robustly, Query & TryUntil are now callable directly on session. - -# Version 0.2.1 - -Release date: 2011-06-25 - -### Added -Expose the RobustWrapper on Session exposing Robustly(), Query() and TryUntil() directly to help with custom waits & retries - -# Version 0.2.0 - -Release date: 2011-06-20 - -### Dependencies -* Upgrade to Selenium 2.0 RC2 (package Selenium directly again, until it becomes available as a nuget dependency) - -### Added -* Hover(Func) -* Click(Func) -* Scoping within iframes -* Support id ends with, for buttons and fields -* HasContentMatch for regex, HasContent for text - -### Changed -* Convert to VS2010, reference selenium+watin nuget packages, refine end-to-end examples -* SeleniumWebDriver optimisation -* End session checks for ActiveSession - -# Version 0.1.3 - -Release date: 2011-05-27 - -2011-05-27 - -### Added -* WithinFieldset and WithinSection -* Configurable wait between find & click -* Much more support for WatiN driver (see driver_test_results.txt) -* MIT Licence -* Nuget - -### Changed -* Section headers may contain other markup, e.g. links -* Reuse scope within individual driver methods -* Close any alerts on disposing SeleniumWebDriver - -# Version 0.1.2 - -Release date: 2011-05-12 - -### Added - -* Configure the AppHost, Port and SSL globally -* Simple ExecuteScript in SeleniumWebDriver -* Set file upload paths with FillIn.With -* Scoping of HasContent -* ClickLink and ClickButton now have TryUntil overloads - -### Changed - -* Visit now takes a virtual path -* Renamed WaitAndRetryRobustWrapper to RetryUntilTimeoutRobustWrapper - - +# Coypu 4.1.0 - 09.12.2023 +* [#231](https://github.com/featurist/coypu/pull/231) Playwright driver + +# Coypu 3.1.2 - 4.0.0 +* History not recorded + +# Coypu 3.1.1 - 19.07.2019 +* [#203](https://github.com/featurist/coypu/issues/203) fix for saving file as png saves the contents as jpg for .Net Core - Thanks to @basimc4 for PR + +Can be also downloaded as nuget packages from https://www.nuget.org/packages/Coypu/ + +# Coypu 3.1.0 - 17.07.2019 +* [#205](https://github.com/featurist/coypu/issues/205) Firefox fails to switch out of iframe - Thanks to @Hawxy for PR +* plus small changes in tests + +Can be also downloaded as nuget packages from https://www.nuget.org/packages/Coypu/ + +# Coypu 3.0.1 - 13.12.2018 +* [#199](https://github.com/featurist/coypu/issues/199) removing nuspec related files to allign with new approach of creating nuget packages from csproj files + +Can be also downloaded as nuget packages from https://www.nuget.org/packages/Coypu/ + +## Coypu.NUnit 3.0.1 - 13.12.2018 +* nuspec still used to generate nuget package + +Can be also downloaded as nuget package from https://www.nuget.org/packages/Coypu.NUnit/ + +# Coypu 3.0.0-rc - 11.12.2018 +Can be also downloaded as nuget package from: https://www.nuget.org/packages/Coypu/ +### Breaking changes +* support for .NET Framework 4.5+ +* support for .NET Standard 2.0+ +* [#144](https://github.com/featurist/coypu/issues/144) support for .NET Core 2.0+ +* updated Selenium WebDriver to 3.141.0 +* removed all projects and dependencies related to WatiN +* removed methods from DriverScope and IScope which has been marked as obsolete since 2014: + - HasCss() + - HasNoCss() + - HasXPath() + - HasNoXPath() +### Fixes +* [#92](https://github.com/featurist/coypu/issues/92) Added the ability to pass in separate options to the `Select("value", selectOptions).From("selectId", fromOptions)` methods +* [#189](https://github.com/featurist/coypu/issues/189) fixed by update of selenium webdriver to v3.141.0 +* [#146](https://github.com/featurist/coypu/issues/146) and [#164](https://github.com/featurist/coypu/issues/164) fix for WaitBeforeClick for general Click(), previously option WaitBeforeClick worked only for ClickButton() and ClickLink() methods +* various fixes for failing unit, driver and acceptance tests +### Changes +* added new Cookies class with methods which are available from `_browserSession.Driver.Cookies`: + - AddCookie(cookie) + - DeleteAll() + - DeleteCookie(cookie) + - DeleteCookieNamed(cookieName) + - GetCookieNamed(cookieName) + - WaitUntilCookieExists(cookie, options) +* methods marked as obsolete: + - GetBrowserCookies() -> use instead `_browserSession.Driver.Cookies.GetAll()` + - ClearBrowserCookies() -> use instead `_browserSession.Driver.Cookies.DeleteAll()` +* migrate solution projects to new VS2017 format +* updated browser drivers to newest versions (geckodriver, chromedriver, iedriver) + +## Coypu.NUnit 3.0.0-rc - 11.12.2018 +### Breaking changes +* support for .NET Framework 4.5+ +* support for .NET Standard 2.0+ +* [#144](https://github.com/featurist/coypu/issues/144) support for .NET Core 2.0+ +* updated Coypu to v3.0.0-rc + +# Version 2.11.0 + +### Updated + +* Update min Selenium dependency to 3.0.1 + +Release date: 2016-12-06 + + +# Version 2.10.0 + +### Updated + +* Update min Selenium dependency to 2.48.0 + +Release date: 2015-10-23 + + +# Version 2.9.1 + +### Added + +* Fix FindXPath with text to use xpath builder rather than slow post filter + +Release date: 2015-05-29 + + +# Version 2.9.0 + +### Added + +* FindXPath takes a text parameter like FindCss + +Release date: 2015-05-29 + + +# Version 2.8.0 + +### Added + +*Added possibility to pass arguments to executeScript #128 (@drauch) + +Release date: 2015-01-26 + + +# Version 2.7.0 + +### Added (incl Coypu.WatiN 2.2.0) + +* ElementScope gets Disabled property - Closes #123 + +Release date: 2014-12-12 + + +# Version 2.6.1 + +### Fixed + +* Fixes #121 - stale element ref exception calling FindAllCss/XPath on stale outer scope + +Release date: 2014-12-03 + + +# Version 2.6.0 + +### Fixed + +* Handle stale elements/windows by exceptions rather than explicit stale checks - fixes #112 AcceptModalDialog() fails in IE11 +* Override timeout in Exists() and Missing() +* NoAlertPresentException when Disposing Chrome Browser +* ElementScope's properties (HTML attribute access) race condition - fixes #117 + +Release date: 2014-11-30 + +# Version 2.5.1 + +### Fixed + +* Fix AppHost to accept basic auth userinfo + +# Version 2.5.0 + +### Added + +* Added CLSCompliant(true) to all production assemblies. (@drauch) + +### Fixed + +* Firefox webdriver 2.43.0 has started closing alerts when asked for currentWindowHandle + +### Changed + +* Thanks to fix for Firefox Webdriver, if you switch window via the native selenium driver +coypu will no longer be able to switch back to the correct window if you use a pre-existing scope +so use the native SwitchTo with caution. + +# Version 2.4.1 + +### Fixed + +* Only switch to window scope when necessary +* Support more html5 input types, fixes #104 + +Release date: 2014-07-25 + + +# Version 2.4.0 + +### Added + +* Parameterized BrowserSession to take in a Driver (@Br3ttl3y) + +Release date: 2014-07-25 + + +# Version 2.3.0 + +### Added + +* Upgrade to WebDriver 2.41 +* Android dri ver removed from WebDriver 2.41 + +Release date: 2014-04-01 + + +# Version 2.2.0 + +### Added + +* Scope.FindIdEndingWith() for Asp.Net Web Forms +* ElementScope.SelectOption() for finding a select then picking an option later + +Release date: 2014-03-18 + + +# Version 2.1.4 + +### Fixed + +* Fixed issue where Options.ToString() was causing a stack overflow fixes #85 (@adexios) + +Release date: 2014-02-12 + + +# Version 2.1.3 + +### Fixed + +* Fix null reference in queries with snapshot scopes (FindALL) fixes #84 + +Release date: 2014-02-04 + + +# Version 2.1.2 + +### Fixed + +* Find select and options seperately to allow differing TextPrecisions when PreferExact + +Release date: 2014-01-31 + + +# Version 2.1.1 + +### Fixed + +* Exact text not matching selects found by container labels + +Release date: 2014-01-31 + + +# Version 2.1.0 + +### Changed + +* NUnit matchers split out into seperate package Coypu.NUnit and Coypu.NUnit262 for those that really need NUnit 2.6.2 + +Release date: 2014-01-30 + + +# Version 2.0.1 + +### Added + +* New Exact and Match options (http://www.featurist.co.uk/blog/2014/01/22/coypu-2-0/) + +### Changed + +* Exact = false by default - all finders match partial text by default +* Match.Single by default - more than one matching element throws AmbiguousException +* Depend on same major version of dependencies + +### Fixed + +* On individual calls, only specified options override session configuration + +### Removed + +* HasCss, HasXPath etc - Just use .Exists()/.Missing() on any scope or Assert.That(scope, Shows.Css(".summat")) + +Release date: 2014-01-02 + +# Version 1.0.0 + +### Updated + +* Selenium WebDriver 2.39.0 + +Release date: 2013-12-20 + + +# Version 0.23.1 + +### Fix + +* Stack overflow in Shows.CssContaining :o + +Release date: 2013-11-21 + + +# Version 0.23.0 + +### Added + +* History: BrowserWindow.GoBack() and BrowserWindow.GoForward() + +Release date: 2013-11-18 + + +# Version 0.22.2 + +### Fixed + +* Actual values in matcher feedback could be wrong +* Fix for text of an iframe + +Release date: 2013-11-08 + + +# Version 0.22.1 + +### Fixed + +* Radio buttons should NOT be found by name + +Release date: 2013-11-06 + + +### Added + +* Support for url input type (@ajh79) +* Shows.Value; Shows.No.Value; HasValue; HasNoValue on ElementScope (@kberridge) +* BrowserWindow.Refresh() (@stefanforsberg) + +### Updated + +* Latest NUnit + +Release date: 2013-11-06 + + +# Version 0.21.1 + +### Fixed + +* Correct dependency on Selenium WebDriver 2.37.0 + +Release date: 2013-10-29 + +# Version 0.21.0 + +Release date: 2013-10-28 + +### Added + +* InnerHTML and OuterHTML properties +* NotSupportedException finding invisible elements by text in WebDriver + +### Updated + +* Selenium WebDriver 2.37.0 + +### Fixed + +* Null reference exception when waiting for invisible elements to appear + +# Version 0.20.1 + +Release date: 2013-10-18 + +### Fixed + +* fixes #70 Inputs with no type attribute can now be found by name/id/placeholder + +# Version 0.20.1 + +Release date: 2013-10-18 + +### Added + +* Shows.AllCssInOrder and ContainsCss matchers for assertions against multiple css matches + +# Version 0.19.3 + +Release date: 2013-09-26 + +### Updated + +* Performance improvements for WebDriver + +# Version 0.19.2 + +Release date: 2013-09-25 + +### Fixed + +* Find state (no options) was not respecting session configuration + + +# Version 0.19.1 + +Release date: 2013-09-10 + +### Updated + +* Selenium WebDriver 2.35.0 + + +# Version 0.19.0 + +Release date: 2013-07-31 + +### Added + +* Windows found by title contains if no exact match + +### Updated + +* Selenium WebDriver 2.33.0 +* Refactoring to single xpaths for performance & standardisation across drivers + +### Removed + +* Fields and buttons are no longer found by ID ends with, only an exact match of ID + + +# Version 0.18.0 + +Release date: 2013-06-18 + +### Added + +* SaveScreenshot(fileName) + + +# Version 0.17.0 + +Release date: 2013-06-17 + +### Added + +* MaximiseWindow + +#### Fixed + +* Visit now operates on the correct window in all browsers +* WatiNDriver gets a bunch of fixes for window scope (ExecuteScript, Dialogs, Visit) +* WatiNDriver now supports Title and Location +* WatinDriver now passes the "tricky" window tests i.e. window references survive popups being closed and reopened just like the selenium driver does. + + +# Version 0.16.3 + +Release date: 2013-06-04 + +### Removed + +* No longer a dependency on Newtonsoft.Json + + +# Version 0.16.2 + +Release date: 2013-05-07 + +### Updated + +* Selenium.WebDriver 2.32.1 +* WebDrver selects now use Selenium.Support +* iframes found by css/xpath/id can be used as scopes (WebDriver only) + + +# Version 0.16.1 + +Release date: 2013-04-22 + +### Fixed + +* FindCss with exact text now escapes regex chars correctly + + +# Version 0.16.0 + +Release date: 2013-04-19 + +### Added + +* FindCss can now take required text for it to match an element +* HasCss and HasNoCss use get text filter via FindCss, and matchers + + +# Version 0.15.2 + +Release date: 2013-04-18 + +### Fixed + +* One more place left to clean up forceAllEvents + + +# Version 0.15.1 + +Release date: 2013-04-18 + +### Fixed + +* Left forceAllEvents hanging in the API by mistake + +# Version 0.15.0 + +Release date: 2013-04-18 + +### Added + +* FindAllCss and FindAllXPath now take a predicate which will be retried + +### Removed + +* Remove default fill in via Javascript. Native key events always fire now. + + +# Version 0.14.0 + +Release date: 2013-03-20 + +### Added + +* Added FillInWith/Check/Uncheck to ElementScope (@refractalize) + +### Removed + +* BrowserSession.FillIn(ElementScope) (@refractalize) + + +# Version 0.13.0 + +Release date: 2013-03-07 + +### Added + +* WebDriver now mostly supports PhantomJS so added to Coypu Browsers. Tests for iframes and modal dialogs fail in SeleniumWebdriver+PhantomJS + +# Version 0.12.8 + +Release date: 2012-03-07 + +### Updated + +* Configuration.AppHost may contain http:// or https:// + +# Version 0.12.7 + +Release date: 2013-03-06 + +### Fixed + +* WebDriver dependency is now correct: 2.31.1 + +# Version 0.12.6 + +Release date: 2013-03-04 + +### Updated + +* WebDriver 2.31.1 + +# Version 0.12.5 + +Release date: 2013-02-22 + +# Added Shows.Content and Shows.No.Content NUnit matchers + +# Version 0.12.4 + +Release date: 2013-02-15 + +### Updated + +* WebDriver 2.29.1 + +# Version 0.12.3 + +Release date: 2012-12-12 + +### Updated + +* WebDriver 2.28 + +# Version 0.12.2 + +Release date: 2012-12-04 + +### Fixed + +* Fixes #37 WATIN respect forceAllEvents (@AidenMontgomery) +* Fixes #36 Allow interaction with input fields of type tel (@AidenMontgomery) + +# Version 0.12.1 + +Release date: 2012-11-28 + +### Updated + +* Any element with class 'btn' is considered a button to match bootstrap convention + +# Version 0.12.0 + +Release date: 2012-11-13 + +### Fixed + +* FillIn now works when passed and ElementScope + +### Added + +* SendKeys added to ElementFound + +# Version 0.11.1 + +Release date: 2012-11-13 + +### Updated + +* FillIn only clicks on element first when forceAllEvents is set + +# Version 0.11.0 + +Release date: 2012-11-07 + +### Updated + +* FindAll API now returns a list of snapshot element scopes + +# Version 0.10.3 + +Release date: 2012-11-07 + +### Updated + +* WebDriver 2.26.0 + +### Fixed + +* Dialogs scoped correctly to windows + - Fixes non user-triggered dialogs accessed directly after interacting with a different window + +# Version 0.10.2 + +### Updated + +* WebDriver 2.25.1 +* When a window is missing a MissingWindowException is thrown, rather than MissingHtmlException + +# Version 0.10.0 + +Release date: 2012-06-20 + +### Updated + +* WebDriver 2.23 +* Elements are only found by partial ID if there is a leading underscore + - This was only intended for asp.net webforms testing and was causing too many collisions +* Removed all Obsolete methods left over from < 0.8.0 + +# Version 0.8.10 + +Release date: 2012-05-01 + +### Added + +* Expose window title + +# Version 0.8.9 + +Release date: 2012-04-24 + +### Fixed + +* Dependency on WebDriver 2.21 broken + +# Version 0.8.8 + +Release date: 2012-04-23 + +### Added + +* Browser.Parse() helper for dynamic configuration of browser + +# Version 0.8.7 + +Release date: 2012-04-23 + +### Added + +* FindFrame finds both iframes and old school frameset frames, FindIframe is now obsolete. + +### Fixed + +* Location respects window scope in all cases + +# Version 0.8.6 + +Release date: 2012-04-18 + +### Updated + +* WebDrier 2.21 - Fixes hover + +# Version 0.8.5 + +Release date: 2012-04-12 + +### Fixed + +* Coypu.xml not being packaged + +# Version 0.8.4 + +Release date: 2012-04-12 + +### Fixed + +* Finds fields with type=email + +# Version 0.8.3 + +Release date: 2012-04-11 + +### Fixed + +* Only yse JS to fillin in js-supporting drivers, i.e. not HtmlUnit + +# Version 0.8.2 + +Release date: 2012-04-05 + +### Fixed + +* FillIn now uses JS by default for speed in WebDriver. + +# Version 0.8.1 + +Release date: 2012-04-05 + +### Updated + +* FillIn now uses JS by default for speed in WebDriver. + +### Fixed + +* Fill in making unneccassary driver calls. + +# Version 0.8.0 + +Release date: 2012-04-03 + +** NOTE: Breaking changes ** + +Check the README for the latest API + +### Removed +* Static configuration and static Browser.Session - use new BrowserSession(Configuration) and manage lifetime yourself. + +### Updated +* Find methods return ElementScope with deferred execution. This replaces the browser.Within() functionality. +* Timing Options can be passed to every method to override configuration. This replaces browser.WithIndividualTimeout() + +### Added +* Nested scopes +* Windows as scopes + +# Version 0.7.3 + +Release date: 2012-03-20 + +### Fixed +* Quit not Close + Dispose fixes HtmlUnit & Chrome not closing properly + +# Version 0.7.2 + +Release date: 2012-02-27 + +### Updated +* Minimum Selenium.WebDriver is now 2.19.0 + +### Added to Coypu.WatiN 0.6.0 +* Watin will now consider invisible elements (citizenmatt) +* Watin can now find by css (citizenmatt) +* Watin handles FindField and partial ID correctly (citizenmatt) +* Watin - Only buttons and fields now match by partial id (citizenmatt) +* Watin frames can now be used as scopes (citizenmatt) +* Watin - Find frames support (citizenmatt) +* Watin - Options in a select list are now selected by text or value (citizenmatt) +* WatiNDriver now supports cookies (citizenmatt) +* WatiNDriver now supports hover (citizenmatt) +* Added support for Accept/CancelModalDialog to WatiNDriver (citizenmatt) +* Added HasDialog support to WatiNDriver (citizenmatt) + +### Changed +* Selenium 2.11.0 or later + +# Version 0.7.0 + +Release date: 2011-02-01 + +### Added +* Simple support for multiple sessions + +# Version 0.6.0 + +Release date: 2011-11-19 + +### Added +* ConsideringInvisibleElements scope added to sessions - for when you reeeeeally need it + +# Version 0.5.4 + +Release date: 2011-11-10 + +### Fixed +* Session.Location not respecting iframe scope + +# Version 0.5.3 + +Release date: 2011-11-06 + +### Changed +* Selenium 2.11.0 or later + +# Version 0.5.2 + +Release date: 2011-10-18 + +### Fixed +* Proj dependencies broken + +# Version 0.5.1 + +Release date: 2011-10-18 + +### Fixed +* Dependency on Newtonsoft.Json via Selenium.WebDriver broken +* You may need to Uninstall-Package for Coypu, Selenium.WebDriver and Newtonsoft.Json then Install-Package Coypu + +# Version 0.5.0 + +Release date: 2011-10-11 + +### Added +* HtmlUnit help + +### Changed +* Upgrade to Selenium.WebDriver 2.7.0 + +### BREAKING CHANGE +* Timeout default down from 10 to 1 sec + +# Version 0.4.0 + +Release date: 2011-09-02 + +### Added +* Expose browser's current location + +### Changed +* Split out WatiN driver into seperate Coypu.Watin package, only WebDriver is included in Coypu by default. +* Reference official Selenium on nuget and make a dependency of Coypu.nuget + +### Fixed +* Error on second use of IFrame scope within the same block + +# Version 0.3.0 + +Release date: 2011-08-02 + +### Fixed +* FindSection not working across all versions of Firefox + +### Changed +* Will bump the minor version for any API/behaviour changes from now on as I should have been doing - bump to 0.3.0 to break with 0.2.x + +# Version 0.2.10 + +Release date: 2011-07-25 + +### Fixed +* FindSection patched to remove compound css selector after this broke in WebDriver 2.1 + +### Added +* Overload for Within that returns a result from within the given scope + +# Version 0.2.9 + +Release date: 2011-07-21 + +### Changed +* Update to Selenium WebDriver 2.1.0 + +# Version 0.2.8 + +Release date: 2011-07-18 + +### Added +* SaveWebResource saves a resource from the web directly using the current browser session's cookies +* Find buttons by role='button' attr + +### Fixed +* Escaping both types of quotes in xpath literals + +# Version 0.2.7 + +Release date: 2011-07-12 + +### Added +* FindState - finds the first to be reached from a list of possible states the page may be in + +### Fixed +* Don't click in file fields, was bringing up the browse dialog in some browsers + +# Version 0.2.6 + +Release date: 2011-07-04 + +### Changed +* Selenium WedDriver RC3 is now on nuget, so it's a dependency again. + +### Added +* Enabled AndroidDriver for Selenium +* SeleniumWebDriver now exposes a constructor overload that takes in an instance of RemoteWebDriver. This allows you to create your own derived drivers that can make use of the RemoteWebDriver (e.g. using HtmlUnitDriver) (citizenmatt) + +# Version 0.2.5 + +Release date: 2011-07-03 + +### Fixed +* Fix issue #13 - HasNo methods are knackered + +# Version 0.2.4 + +Release date: 2011-06-30 + +### Added +* Adding doc comments to Session API + +### Changed +* Bunch of stuff that should have been internal + +# Version 0.2.3 + +Release date: 2011-06-27 + +### Changed +* Upgrade of Selenium WebDriver to 2.0rc3 + +# Version 0.2.2 + +Release date: 2011-06-27 + +### Added +* Rounded out the session API with FindSection, FindFieldset and FindId and make the Session.Driver internal. +* Has(Func) and HasNot(Func) for custom queries. +* Robustly, Query & TryUntil are now callable directly on session. + +# Version 0.2.1 + +Release date: 2011-06-25 + +### Added +Expose the RobustWrapper on Session exposing Robustly(), Query() and TryUntil() directly to help with custom waits & retries + +# Version 0.2.0 + +Release date: 2011-06-20 + +### Dependencies +* Upgrade to Selenium 2.0 RC2 (package Selenium directly again, until it becomes available as a nuget dependency) + +### Added +* Hover(Func) +* Click(Func) +* Scoping within iframes +* Support id ends with, for buttons and fields +* HasContentMatch for regex, HasContent for text + +### Changed +* Convert to VS2010, reference selenium+watin nuget packages, refine end-to-end examples +* SeleniumWebDriver optimisation +* End session checks for ActiveSession + +# Version 0.1.3 + +Release date: 2011-05-27 + +2011-05-27 + +### Added +* WithinFieldset and WithinSection +* Configurable wait between find & click +* Much more support for WatiN driver (see driver_test_results.txt) +* MIT Licence +* Nuget + +### Changed +* Section headers may contain other markup, e.g. links +* Reuse scope within individual driver methods +* Close any alerts on disposing SeleniumWebDriver + +# Version 0.1.2 + +Release date: 2011-05-12 + +### Added + +* Configure the AppHost, Port and SSL globally +* Simple ExecuteScript in SeleniumWebDriver +* Set file upload paths with FillIn.With +* Scoping of HasContent +* ClickLink and ClickButton now have TryUntil overloads + +### Changed + +* Visit now takes a virtual path +* Renamed WaitAndRetryRobustWrapper to RetryUntilTimeoutRobustWrapper diff --git a/README.md b/README.md index 09676159..008560a4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # Coypu [![Nuget](https://img.shields.io/nuget/v/Coypu.svg)](https://www.nuget.org/packages/Coypu/) [![Nuget](https://img.shields.io/nuget/dt/Coypu.svg)](https://www.nuget.org/packages/Coypu/) ![](https://img.shields.io/badge/compatibility-.NET%20Framework%204.5%2B%20%7C%20.NET%20Standard%202.0-blue.svg) +## "Theirs not to reason why, Theirs but to do and retry" +— Alfred, Lord Selenium +## "haha, coypu must have reduced teh amount of shit code by 90%" +— Anonymous + Coypu supports browser automation in .Net to help make tests readable, robust, fast to write and less tightly coupled to the UI. If your tests are littered with sleeps, retries, complex XPath expressions and IDs dug out of the source with browser developer tools then Coypu might help. Coypu is on Nuget: @@ -13,23 +18,21 @@ NUnit matchers (e.g. `Assert.That(browserSession, Shows.Content("Hello world")); Discuss Coypu and get help on the [Google Group](http://groups.google.com/group/coypu) ## Coypu is -* A robust wrapper for browser automation on .net platform for Selenium WebDriver that eases automating ajax-heavy websites and reduces coupling to the HTML, CSS & JS -* A more intuitive DSL for interacting with the browser in the way a human being would, inspired by the ruby framework Capybara - http://github.com/jnicklas/capybara +* A robust wrapper for browser automation on .net platform for [Selenium WebDriver](https://www.selenium.dev/documentation/webdriver/) or [Microsoft Playwright](https://playwright.dev) that eases automating ajax-heavy websites and reduces coupling to the HTML, CSS & JS +* A more intuitive API for interacting with the browser in the way a human being would, inspired by the ruby framework Capybara - http://github.com/jnicklas/capybara ## Demo -Check out a [demo of Coypu](http://skillsmatter.com/podcast/open-source-dot-net/london-dot-net-user-group-may) from a talk given at Skills Matter in May 2011. - -## Using Coypu +Check out a [demo of Coypu](http://skillsmatter.com/podcast/open-source-dot-net/london-dot-net-user-group-may) from a talk given at Skills Matter way back in May 2011. -#### Browser session +## Browser session Open a browser session like so: ```c# var browser = new BrowserSession(); ``` - + When you are done with the browser session: ```c# @@ -39,13 +42,13 @@ browser.Dispose(); or: ```c# -using (var browser = new BrowserSession()) +using (var browser = new BrowserSession()) { ... } ``` - -### Configuration + +## Configuration To configure Coypu pass an instance of `Coypu.SessionConfiguration` to the constructor of BrowserSession: @@ -53,12 +56,12 @@ To configure Coypu pass an instance of `Coypu.SessionConfiguration` to the const var browserSession = new BrowserSession(new SessionConfiguration{...}); ``` -#### Website under test +## Website under test Configure the website you are testing as follows ```c# -var sessionConfiguration = new SessionConfiguration +var sessionConfiguration = new SessionConfiguration { AppHost = "autotrader.co.uk", Port = 5555, @@ -68,7 +71,7 @@ var sessionConfiguration = new SessionConfiguration If you don't specify any of these, Coypu will default to http, localhost and port 80. -#### Driver +## Driver Coypu drivers implement the `Coypu.Driver` interface and read the `SessionConfiguration.Browser` setting to pick the correct browser. @@ -78,7 +81,7 @@ Choose your driver/browser combination like so: sessionConfiguration.Driver = typeof (SeleniumWebDriver); sessionConfiguration.Browser = Drivers.Browser.Firefox; ``` - + These settings are the default configuration. If you want to configure these at runtime you could replace the following strings with strings read from your environment / configuration: @@ -88,31 +91,75 @@ sessionConfiguration.Driver = Type.GetType("Coypu.Drivers.Selenium.SeleniumWebDr sessionConfiguration.Browser = Drivers.Browser.Parse("firefox"); ``` -##### Selenium WebDriver +## Headless mode + +```c# +sessionConfiguration.Headless=true +``` + +**The Playwright driver in headless mode passes all the Coypu specs. Headless Playwright runs up to 5 times faster than headed Selenium.** + +Headless was never enabled on the Selenium driver as it had various shortcomings in the past, from Coypu v4.1.0 it is can be enabled using the `sessionConfiguration.Headless` setting. Selenium does have issues accessing cookies and IFrames in headless mode so you may need to run tests that rely on these features in headed mode when using the Selenium driver. + +Selenium only supports headless for Chrome, Edge and Firefox. IE, Safari and Opera are not supported. + +## Playwright + +Coypu v4.1.0 adds a driver for the Playwright framework. Playwright is a modern automation framework from Microsoft that is faster and more reliable than Selenium. Playwright supports Chrome, Edge, Firefox and Webkit (Safari). + +The playwright driver will become the default driver for Coypu in the next major release. + +[A note on what Playwright means for Coypu](#a-note-on-playwright) + +```c# +sessionConfiguration.Driver = typeof (Playwright); +``` + +### Install browser binaries + +You may need to install the latest playwright browser binaries using the Playwright CLI: + +https://playwright.dev/dotnet/docs/browsers#install-browsers + + +### Playwright supports: + +```c# +Copyu.Browser.Chromium +Copyu.Browser.Chrome +Copyu.Browser.Edge +Copyu.Browser.Firefox +Copyu.Browser.Webkit +``` + +More on these options: https://playwright.dev/dotnet/docs/browsers + +## Selenium WebDriver + `Coypu.Drivers.Selenium.SeleniumWebDriver` tracks the latest version of WebDriver and supports Chrome (fastest), Firefox, Edge and IE (slowest) as the browser. Any other Selenium implementation of RemoteWebDriver can be configured by subclassing `SeleniumWebDriver` and passing an instance of RemoteWebDriver to the base constructor. The Selenium Driver is included in the Coypu package. -###### Chrome +### Chrome You will need the chromedriver.exe on your PATH or in the bin of your test project. We recommend adding the nuget package `Selenium.WebDriver.ChromeDriver` to your project. -###### Firefox +### Firefox You will need GeckoDriver. We recommend adding the nuget package `Selenium.WebDriver.GeckoDriver.Win64` to your project. -###### Edge +### Edge You will need Microsoft's WebDriver. How you install this depends on your version of Windows 10. Please see [here](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/) for more information. -###### Internet Explorer +### Internet Explorer You will need the new standalone InternetExplorerDriver.exe in your PATH or in the bin of your test project. We recommend adding the nuget package `Selenium.WebDriver.IEDriver` package to your project. Please see [Configuration Requirements](https://github.com/SeleniumHQ/selenium/wiki/InternetExplorerDriver#required-configuration) for information on how to use it. -### SpecFlow scenarios +## SpecFlow scenarios If you are using SpecFlow for your acceptance tests then you will probably want to configure it to provide a single Browser Session scoped to each scenario. SpecFlow supports some basic dependency injection which you can use to achieve this as shown in [this gist](https://gist.github.com/2301407). - -### Waits, retries and timeout -Most of the methods in the Coypu DSL are automatically retried on any driver error until a configurable timeout is reached. It just catches exceptions and retries -- mainly the `Coypu.Drivers.MissingHtmlException` that a driver should throw when it cannot find something, but also any internal driver errors that the driver might throw up. +## Waits, retries and timeout + +Most of the methods in the Coypu API are automatically retried on any driver error until a configurable timeout is reached. It just catches exceptions and retries -- mainly the `Coypu.Drivers.MissingHtmlException` that a driver should throw when it cannot find something, but also any internal driver errors that the driver might throw up. This is a rather blunt approach that goes well beyond WebDriver's ImplicitWait, for example, but the only truly robust strategy for heavily asynchronous websites, where elements are flying in and out of the DOM constantly, that I have found. @@ -124,10 +171,10 @@ Configure timeout/retry like so: sessionConfiguration.Timeout = TimeSpan.FromSeconds(1); sessionConfiguration.RetryInterval = TimeSpan.FromSeconds(0.1); ``` - + These settings are the default configuration. -All methods in the DSL take an optional final parameter of a `Coypu.Options`. By passing this in you can override any of these timing settings for just that call: +All methods in the API take an optional final parameter of a `Coypu.Options`. By passing this in you can override any of these timing settings for just that call: So when you need an unusually long (or short) timeout for a particular interaction you can override the timeout just for this call by passing in a `Coypu.Options` like this: @@ -139,38 +186,38 @@ Assert.That(browser, Shows.Content("File bigfile.mp4 (10.5mb) uploaded successfu The options you specify are merged with your SessionConfiguration, so you only need specify those options you wish to override. -### Visible elements +## Visible elements -Coypu drivers filter out any elements that are not visible on the page -- this includes hidden inputs. +Coypu drivers filter out any elements that are not visible on the page -- this includes hidden inputs. -Non-visible elements can get in the way of finding the elements that we are really looking for and cause often errors when trying to interact with them. +Non-visible elements can get in the way of finding the elements that we are really looking for and cause often errors when trying to interact with them. What we are really trying to do here is interact with the browser in the way that a human would. It's probably best to avoid hacking around with elements not accessible to the user where possible to avoid invalidating our tests in any case. -#### However... +### However... If you really need this for some intractable problem where you cannot control the browser without cheating like this, then there is `sessionConfiguration/options.ConsideringInvisibleElements = true` which overrides this restriction. -### Can't find what you need? +## Can't find what you need? -If there's something you need that's not part of the DSL then please you may need to dive into the native driver which you can always do by casting the native driver to whatever underlying driver you know you are using: +If there's something you need that's not part of the API then you may need to dive into the native driver which you can always do by casting the native driver to whatever underlying driver you know you are using: ```c# var selenium = ((OpenQA.Selenium.Remote.RemoteWebDriver) browserSession.Native); ``` - + But if you need to do this, please consider forking Coypu, adding what you need and sending a pull request. Thanks! -### DSL +# API Here are some examples to get you started using Coypu - -#### Navigating -```c# +## Navigating + +```c# browser.Visit("/used-cars"); ``` - + If you need to step away and visit a site outside of the `SessionConfiguration.AppHost` then you can use a fully qualified Uri: ```c# @@ -185,15 +232,15 @@ browser.GoBack(); browser.GoForward(); ``` -#### Getting the page title +## Getting the page title ```c# browser.Title ``` - -#### Completing forms + +## Completing forms Form fields are found by label text, id, name (except radio buttons), placeholder or radio button value - + ```c# // Drop downs browser.Select("toyota").From("make"); @@ -210,7 +257,7 @@ browser.Choose("Private"); // Checkboxes browser.Check("Additional ads") -browser.Uncheck("Additional ads") +browser.Uncheck("Additional ads") ``` If you need to fall back to CSS or XPath you can do: @@ -235,9 +282,9 @@ or browser.FindCss("ul.model li", text: new Regex("Citroen C\d")); ``` -#### Clicking +## Clicking -Buttons are found by value/text, id or name. +Buttons are found by value/text, id or name. ```c# browser.ClickButton("Search"); @@ -252,14 +299,14 @@ browser.ClickLink("Reset search"); Click any other element by calling the Click method on the returned `ElementScope`: -```c# +```c# browser.FindCss("span#i-should-be-a-link", text: "Log in").Click(); ``` In this example, due to the way Coypu defers execution of finders, the FindCss will also be retried, should the Click fail. For example if the DOM is shifting under the driver's feet, the link may have become stale after it is found but before the click is actioned while part of the page is reloaded. This introduces the idea of `Scope`. The browser.Find methods return a Scope on which you may perform actions, or make further scoped queries. There is more on scope below. - + The last way to click is to pass an element you have already found directly to `Click()`: ```c# @@ -270,7 +317,7 @@ foreach(var element in allToClick) } ``` -#### Finding single elements +## Finding single elements Find methods return a `Coypu.ElementScope` that is scoped to the first matching element. The locator arguments are case sensitive. @@ -298,12 +345,12 @@ You can read attributes of these elements like so: browser.FindLink("Home")["rel"] ``` -#### Finding multiple elements - +## Finding multiple elements + FindAll methods return all matching elements immediately with no retry: ```c# - foreach(var link in browser.FindAllCss("a")) + foreach(var link in browser.FindAllCss("a")) { var attributeValue = a["href"]; ... @@ -312,13 +359,13 @@ FindAll methods return all matching elements immediately with no retry: If you are expecting a particular state to be reached then you can describe this in a predicate and Coypu will retry until it matches. - foreach(var link in browser.FindAllCss("a", (links) => links.Count() == 5)) + foreach(var link in browser.FindAllCss("a", (links) => links.Count() == 5)) { var attributeValue = a["href"]; ... } -#### Matching exactly or allowing substrings +## Matching exactly or allowing substrings When finding elements by their text, the `TextPrecision` option allows you to specify whether to match exact text or allow a substring match. This can be set globally and also overridden on each and every call. `TextPrecision` has three options: `Exact`, `Substring` and `PreferExact`. The default is `PreferExact`. @@ -328,7 +375,7 @@ When finding elements by their text, the `TextPrecision` option allows you to sp `TextPrecision.PreferExact` which will prefer an exact text match to a substring match. **This is the default for TextPrecision** -##### Usage +## Usage ```c# browserSession.FillIn("Password", new Options{TextPrecision = TextPrecision.Exact}).With("123456"); @@ -338,7 +385,7 @@ browserSession.FillIn("Password", Options.Exact).With("123456"); This will be respected everywhere that Coypu matches visible text, including buttons, select options, placeholder text and so on, but not anywhere else, for example when considering ids or names of fields. -#### Behaviour when multiple elements match a query +## Behaviour when multiple elements match a query When using methods such as `ClickLink()`, and `FillIn()`, what happens when more than one element matches? With the `Match` option you have control over what happens by choosing one of the two `Match` strategies: @@ -347,7 +394,7 @@ When using methods such as `ClickLink()`, and `FillIn()`, what happens when more `Match.First` just returns the first matching element. -##### Usage +## Usage ```c# browserSession.ClickButton("Close", new Options{Match = Match.First}); @@ -355,7 +402,7 @@ browserSession.ClickButton("Close", new Options{Match = Match.First}); browserSession.ClickButton("Close", Options.First); ``` -#### Some more examples of using TextPrecision and Match +## Some more examples of using TextPrecision and Match Say we had the HTML: @@ -391,28 +438,28 @@ then as we vary the values of text and the options these would be the results: | "good things" | First | Substring | Clicks the link to 'x' | | "good things" | First | PreferExact | Clicks the link to 'x' | -#### Hover +## Hover Hover over an element browser.FindCss("span#hoverOnMe").Hover(); -#### Fieldsets / Sections +## Fieldsets / Sections To find this: -
+
Advanced search ...
-use this: - +use this: + var element = browser.FindFieldset("Advanced search"); - + To find this: -
+

Search results

...
@@ -425,12 +472,12 @@ or this: use this: - + var element = browser.FindSection("Search results"); **These work particularly well when used as scopes:** -#### Scope +## Scope When you want perform operations only within a particular part of the page, find the scope you want then use this as the scope for further finds and interactions as in the previous fieldset/section example. @@ -454,7 +501,7 @@ The actual finding of the scope is deferred until the driver needs to interact w This means you have tests much more loosely coupled to the implementation of your website. Consider the search example above and the possible permutations of HTML and JS that would satisfy that test. -#### Beware the XPath // trap +## Beware the XPath // trap In XPath the expression // means something very specific, and it might not be what you think. Contrary to common belief, // means "anywhere in the document" not "anywhere @@ -475,31 +522,31 @@ browser.FindXPath("//body").FindAllXPath(".//script"); (from https://github.com/jnicklas/capybara#beware-the-xpath--trap) -#### Scoping within frames / iframes +## Scoping within frames / iframes To restrict the scope to a frame or iframe, locate the frame by its name,id, title or the text of an h1 element within the frame: ```c# var twitterFrame = browser.FindFrame("@coypu_news on Twitter"); -Assert.That(twitterFrame, Shows.Content("Coypu 0.8.0 released")); +Assert.That(twitterFrame, Shows.Content("Coypu 0.8.0 released")); ``` -#### Scoping within windows +## Scoping within windows To restrict the scope to a browser window (or tab), locate the window by its title or name: ```c# var surveyPopup = browser.FindWindow("Customer Survey"); -surveyPopup.Select("Not Satisfied").From("How did we handle your enquiry?"); +surveyPopup.Select("Not Satisfied").From("How did we handle your enquiry?"); surveyPopup.ClickButton("Submit"); browser.ClickLink("Logout"); // Using the original window scope again - there is no need to switch back, just use the correct scope ``` If no exact match is found Coypu will consider windows were the title contains the supplied value - + Switching between frames and windows is a particular pain in WebDriver as you may well know. Check out this example of how Coypu handles windows from a Coypu acceptance test: ```c# @@ -527,7 +574,7 @@ button.Click(); **N.B.** If you drop down into the native Selenium driver and use SwitchTo() (highly unrecommended), bypassing Coypu's FindWindow(), Coypu will lose track of the current window, so make sure to switch back to the previous window before dropping back to Coypu. -#### Window size +## Window size Sometimes you need to maximise the window, or to set a particular width, perhaps for testing your responsive layout: @@ -542,28 +589,37 @@ If you are dealing with multiple windows, just call these on the correct scope: browser.FindWindow("Pop Up Window").MaximiseWindow(); ``` -#### Executing javascript in the browser +## Executing javascript in the browser You can execute javascript like so: ```c# browser.ExecuteScript("document.getElementById('SomeContainer').innerHTML = '

Hello

';"); ``` - -Anything is returned from the javascript will be returned from `browser.ExecuteScript` + +Anything returned from the javascript will be returned from `browser.ExecuteScript` ```c# var innerHtml = browser.ExecuteScript("return document.getElementById('SomeContainer').innerHTML;"); -``` - -#### Querying + +```Arguments can be passed and referenced via the arguments array: + +```c# +browser.ExecuteScript("document.getElementById(arguments[0]).innerHTML = '

Hello

'"); + +```Arguments can be `Coypu.Element` objects: + +```c# +browser.ExecuteScript("arguments[0].innerHTML = '

Hello

'"); + +## Querying Look for text anywhere in the page: ```c# bool hasContent = browser.HasContent("In France, the coypu is known as a ragondin"); ``` - + Check for the presence of an element: ```c# @@ -572,7 +628,7 @@ bool hasElement = browser.FindCss("ul.menu > li", text: "Home").Missing(); bool hasElement = browser.FindXPath("//ul[@class = 'menu']/li").Exists(); ``` - + The positive queries above will wait up to the configured timeout for a matching element to appear and return as soon as it does. The negative versions will wait for the element NOT to be present: @@ -593,7 +649,7 @@ bool hasValue = browser.FindField("total").HasValue("147"); bool hasNoValue = browser.FindField("total").HasNoValue("0"); ``` -#### Matchers +## Matchers There are NUnit matchers for some of the queries above to help with your assertions: @@ -613,7 +669,7 @@ Assert.That(browser.FindField("total"), Shows.Value("147")); Assert.That(browser.FindField("total"), Shows.No.Value("0")); ``` -#### Inner/OuterHTML +## Inner/OuterHTML If you just want to grab the inner or outer HTML of an element to do your own queries and assertions you can use: @@ -622,25 +678,29 @@ var outerHTML = browser.FindCss("table#myData").OuterHTML; var innerHTML = browser.FindCss("table#myData").InnerHTML; // Will exclude the surrounding ...
``` -#### Dialogs +## Dialogs -Check for the presence of a modal dialog with expected text: +To interact with dialogs like alerts, confirms and prompts, pass an Action that will trigger the dialog to appear to `AcceptAlert`, `Accept/CancelConfirm` or `Accept/CancelPrompt`. Optionally supply the text that must match the dialog message or an exception will be raised. ```c# -bool hasDialog = browser.HasDialog("Are you sure you want to cancel your account?"); -bool hasNoDialog = browser.HasDialog("Are you sure you want to cancel your account?"); -``` - -Waits are as for the other Has/HasNo methods. +browser.AcceptAlert(() => { + browser.ClickButton("Save"); +}); -Interact with the current dialog like so: +browser.AcceptConfirm("Are you sure you want to cancel your account?", () => { + browser.ClickButton("Cancel my account"); +}); -```c# -browser.AcceptDialog(); -browser.CancelDialog(); +browser.CancelConfirm("Are you sure you want to cancel your account?", () => { + browser.ClickButton("Cancel my account"); +}); + +browser.AcceptPrompt("Please enter your age", "21", () => { + browser.ClickLink("Enter site"); +}); ``` - -#### Finding states (nondeterministic testing) + +## Finding states (nondeterministic testing) Sometimes you just can't predict what state the browser will be in. Not ideal for a reliable test, but if it's unavoidable then you can use the `Session.FindState` like this: @@ -648,7 +708,7 @@ Sometimes you just can't predict what state the browser will be in. Not ideal fo var signedIn = new State(() => browser.HasContent("Signed in in as:")); var signedOut = new State(() => browser.HasContent("Please sign in")); -if (browser.FindState(signedIn,signedOut) == signedIn) +if (browser.FindState(signedIn,signedOut) == signedIn) { browser.ClickLink("Sign out"); } @@ -657,17 +717,17 @@ if (browser.FindState(signedIn,signedOut) == signedIn) It will return as soon as the first from your list of states is found, and throw if none of the states are found within the `SessionConfiguration.Timeout` Avoid this: - + ```c# -if (browser.HasContent("Signed in in as:")) +if (browser.HasContent("Signed in in as:")) { ... } ``` - + otherwise you will have to wait for the full `SessionConfiguration.Timeout` in the negitive case. -### Screenshots +## Screenshots If you can't get the quality of feedback from your tests you need to tell you exactly why they are failing you might need to take a screenshot: @@ -716,7 +776,7 @@ public void CustomProfile() // etc. } - // Or if you need to + // Or if you need to } ``` @@ -765,11 +825,11 @@ public class SauceLabsDriver : SeleniumWebDriver ## More tricks/tips -So, you are using Coypu but sometimes links or buttons still don't seem to be clicked when you expect them to. Well there are a couple more techniques that Coypu can help you with in this situation. +So, you are using Coypu but sometimes links or buttons still don't seem to be clicked when you expect them to. Well there are a couple more techniques that Coypu can help you with in this situation. If the driver reports it had found and clicked your element successfully but nothing happens then it may simply be that your app isn't wiring up events at the right time. But if you have exhausted this angle and cannot fix the problem in the site itself, then you could try a couple of things: -#### Tell Coypu to keep clicking at regular intervals until you see the result you expect: +## Tell Coypu to keep clicking at regular intervals until you see the result you expect: ```c# var until = () => browser.FindCss("#SearchResults").Exists(); @@ -780,14 +840,62 @@ browser.ClickButton("Search", until, waitBetweenRetries); This is far from ideal as you are coupling the click to the expected result rather than verifying what you expect in a separate step, but as a last resort we have found this useful. -#### Tell Coypu to wait a short time between first finding links/buttons and clicking them: +## Tell Coypu to wait a short time between first finding links/buttons and clicking them: ```c# sessionConfiguration.WaitBeforeClick = TimeSpan.FromMilliseconds(0.2); ``` - + WARNING: Setting this in your session configuration means adding time to *every* click in that session. You might be better off doing this just when you need it: ```c# browser.ClickButton("Search", new Options { WaitBeforeClick = TimeSpan.FromMilliseconds(0.2) } ) ``` +## A note on Playwright + +The main reason for adding a Playwright driver to this library thats been making Selenium tests reliable since 2010 is to support those projects with existing Coypu test suites who want to move to Playwright without having to rewrite their tests. A good percentage of these are likely legacy apps but some may have a lot of life left in them if the tests can be kept alive! + +**If you're starting a new project then you should definitely consider using Playwright directly**, it implements a lot of the most useful stuff from Capybara/Coypu which is fantastic. However, if you like the Coypu API, here's a few reasons you might consider using Coypu over Playwrights own API even for a new project: + +### The Capybara/Copyu API is well established and has been pretty stable in the Rails world for a long time. + +The finders in Coypu remain more semantic and direct than Playwrights Locator pattern, and are designed to read like the way a human interacts with a web page without specifying `ByLabel`/`ByPlaceholder` etc e.g.: + +####] Playwright +``` +await page.GetByLabel("Terms & Conditions").CheckAsync(); +await page.GetByLabel("User Name").FillAsync("John"); +``` + +#### Coypu +``` +window.Check("Terms & Conditions"); +window.FillIn("User Name").With("John"); +``` + +You may prefer your tests to read like this, or you might prefer Playwrights more explicit API. + +### Waits and retries +Playwright will auto wait for elements to become actionable in the page for you, but where your page elements change dynamically without a full page reload, like any Single Page App, then you will still need to write an `Expect` assertion for something that will indicate the UI has updated or your test may flake out. + +e.g. To Click through multiple pages loaded with async + +#### Playwright +``` +await Page.GetByRole(AriaRole.Link, new() { Name = "Page 2" }).ClickAsync(); +// AJAX... +await Expect(Page + .GetByRole(AriaRole.Heading, new() { Name = "Page 3" })) + .ToBeVisibleAsync(); +// Ready +await Page.GetByRole(AriaRole.Link, new() { Name = "Page 3" }).ClickAsync(); +``` + +#### Coypu +``` +window.Click("Page 2"); +window.Click("Page 3"); +``` + +### Keeping your options open +As a wrapper that decouples finding elements, interacting with the browser and waits and retries from the actual browser automation you may feel prefer to write against the Coypu API to decouple your tests from your choice of automation framework. Coypu is a small library which can be easily extended to support new drivers, whereas Playwright is a much larger library that you would be more tightly coupled to. diff --git a/src/Coypu.AcceptanceTests/Examples/ApiExamples.cs b/src/Coypu.AcceptanceTests/Examples/ApiExamples.cs index 1b581591..65e58be1 100644 --- a/src/Coypu.AcceptanceTests/Examples/ApiExamples.cs +++ b/src/Coypu.AcceptanceTests/Examples/ApiExamples.cs @@ -1,158 +1,159 @@ -using System; -using Coypu.Drivers; -using Coypu.Drivers.Selenium; -using NUnit.Framework; -using OpenQA.Selenium; -using OpenQA.Selenium.Firefox; - -namespace Coypu.AcceptanceTests.Examples -{ - /// - /// Simple examples for each API method - to show usage and check everything is wired up properly - /// - [TestFixture] - public class ApiExamples : WaitAndRetryExamples - { - public class CustomFirefoxOptionsSeleniumWebDriver : SeleniumWebDriver - { - public CustomFirefoxOptionsSeleniumWebDriver(Browser browser) : base(CustomOptions(), browser) { } - - private static IWebDriver CustomOptions() - { - return new FirefoxDriver(new FirefoxOptions()); - } - } - - [Test] - public void Attributes_on_stale_scope_example() - { - var field = Browser.FindField("find-this-field"); - Assert.That(field.Value, Is.EqualTo("This value is what we are looking for")); - - ReloadTestPage(); - Assert.That(field.Value, Is.EqualTo("This value is what we are looking for")); - Assert.That(field.Id, Is.EqualTo("find-this-field")); - Assert.That(field["id"], Is.EqualTo("find-this-field")); - } - - [Test] - public void Choose_example() - { - Browser.Choose("chooseRadio1"); - Assert.IsTrue(Browser.FindField("chooseRadio1") - .Selected); - - Browser.Choose("chooseRadio2"); - Assert.IsTrue(Browser.FindField("chooseRadio2") - .Selected); - Assert.IsFalse(Browser.FindField("chooseRadio1") - .Selected); - } - - [Test] - public void ConsideringInvisibleElements() - { - Browser.FindButton("firstInvisibleInputId", new Options {ConsiderInvisibleElements = true}) - .Now(); - } - - [Test] - public void ConsideringOnlyVisibleElements() - { - Assert.Throws(() => Browser.FindButton("firstInvisibleInputId") - .Now()); - } - - [Test] - public void CustomOptions() - { - var configuration = new SessionConfiguration {Driver = typeof(CustomFirefoxOptionsSeleniumWebDriver)}; - using (var custom = new BrowserSession(configuration)) - { - custom.Visit("https://www.relishapp.com/"); - Assert.That(custom.ExecuteScript("return 0;"), Is.EqualTo(0)); - } - } - - [Test] - public void DisabledButton_example() - { - Assert.That(Browser.FindButton("Disabled button") - .Disabled, - Is.True, - "Expected button to be disabled"); - Assert.That(Browser.FindButton("Click me") - .Disabled, - Is.False, - "Expected button to be enabled"); - } - - [Test] - public void ExecuteScript_example() - { - ReloadTestPage(); - Assert.That(Browser.ExecuteScript("return document.getElementById('firstButtonId').innerHTML;"), - Is.EqualTo("first button")); - } - - [Test] - public void ExecuteScriptWithArgs_example() - { - ReloadTestPage(); - Assert.That(Browser.ExecuteScript("return arguments[0].innerHTML;", Browser.FindId("firstButtonId")), - Is.EqualTo("first button")); - } - - [Test] - public void Hover_example() - { - Assert.That(Browser.FindId("hoverOnMeTest") - .Text, - Is.EqualTo("Hover on me")); - Browser.FindId("hoverOnMeTest") - .Hover(); - Assert.That(Browser.FindId("hoverOnMeTest") - .Text, - Is.EqualTo("Hover on me - hovered")); - } - - [Test] - public void Multiple_interactions_within_iframe_example() - { - var iframe = Browser.FindFrame("I am iframe one"); - iframe.FillIn("text input in iframe") - .With("filled in"); - Assert.That(iframe.FindField("text input in iframe") - .Value, - Is.EqualTo("filled in")); - } - - [Test] - public void Native_example() - { - var button = (IWebElement) Browser.FindButton("clickMeTest") - .Native; - button.Click(); - Assert.That(Browser.FindButton("clickMeTest") - .Value, - Is.EqualTo("Click me - clicked")); - } - - [Test] - public void Title_example() - { - Assert.That(Browser.Title, Is.EqualTo("Coypu interaction tests page")); - } - - [Test] - public void TryUntil_example() - { - var tryThisButton = Browser.FindButton("try this"); - Assert.That(tryThisButton.Exists()); - Browser.TryUntil(() => tryThisButton.Click(), - () => Browser.HasContent("try until 5"), - TimeSpan.FromMilliseconds(50), - new Options {Timeout = TimeSpan.FromMilliseconds(10000)}); - } - } -} \ No newline at end of file +using System; +using Coypu.Drivers; +using Coypu.Drivers.Selenium; +using Microsoft.Playwright; +using NUnit.Framework; +using OpenQA.Selenium; +using OpenQA.Selenium.Firefox; + +namespace Coypu.AcceptanceTests.Examples +{ + /// + /// Simple examples for each API method - to show usage and check everything is wired up properly + /// + [TestFixture] + public class ApiExamples : WaitAndRetryExamples + { + public class CustomFirefoxOptionsSeleniumWebDriver : SeleniumWebDriver + { + public CustomFirefoxOptionsSeleniumWebDriver(Browser browser, bool headless) : base(CustomOptions(), browser) { } + + private static IWebDriver CustomOptions() + { + return new FirefoxDriver(new FirefoxOptions()); + } + } + + [Test] + public void Attributes_on_stale_scope_example() + { + var field = Browser.FindField("find-this-field"); + Assert.That(field.Value, Is.EqualTo("This value is what we are looking for")); + + ReloadTestPage(); + Assert.That(field.Value, Is.EqualTo("This value is what we are looking for")); + Assert.That(field.Id, Is.EqualTo("find-this-field")); + Assert.That(field["id"], Is.EqualTo("find-this-field")); + } + + [Test] + public void Choose_example() + { + Browser.Choose("chooseRadio1"); + Assert.IsTrue(Browser.FindField("chooseRadio1") + .Selected); + + Browser.Choose("chooseRadio2"); + Assert.IsTrue(Browser.FindField("chooseRadio2") + .Selected); + Assert.IsFalse(Browser.FindField("chooseRadio1") + .Selected); + } + + [Test] + public void ConsideringInvisibleElements() + { + Browser.FindButton("firstInvisibleInputId", new Options {ConsiderInvisibleElements = true}) + .Now(); + } + + [Test] + public void ConsideringOnlyVisibleElements() + { + Assert.Throws(() => Browser.FindButton("firstInvisibleInputId") + .Now()); + } + + [Test] + public void CustomOptions() + { + var configuration = new SessionConfiguration {Driver = typeof(CustomFirefoxOptionsSeleniumWebDriver)}; + using (var custom = new BrowserSession(configuration)) + { + custom.Visit("https://www.relishapp.com/"); + Assert.That(custom.ExecuteScript("return 0;"), Is.EqualTo(0)); + } + } + + [Test] + public void DisabledButton_example() + { + Assert.That(Browser.FindButton("Disabled button") + .Disabled, + Is.True, + "Expected button to be disabled"); + Assert.That(Browser.FindButton("Click me") + .Disabled, + Is.False, + "Expected button to be enabled"); + } + + [Test] + public void ExecuteScript_example() + { + ReloadTestPage(); + Assert.That(Browser.ExecuteScript("return document.getElementById('firstButtonId').innerHTML;"), + Is.EqualTo("first button")); + } + + [Test] + public void ExecuteScriptWithArgs_example() + { + ReloadTestPage(); + Assert.That(Browser.ExecuteScript("return arguments[0].innerHTML;", Browser.FindId("firstButtonId")), + Is.EqualTo("first button")); + } + + [Test] + public void Hover_example() + { + Assert.That(Browser.FindId("hoverOnMeTest") + .Text, + Is.EqualTo("Hover on me")); + Browser.FindId("hoverOnMeTest") + .Hover(); + Assert.That(Browser.FindId("hoverOnMeTest") + .Text, + Is.EqualTo("Hover on me - hovered")); + } + + [Test] + public void Multiple_interactions_within_iframe_example() + { + var iframe = Browser.FindFrame("I am iframe one"); + iframe.FillIn("text input in iframe") + .With("filled in"); + Assert.That(iframe.FindField("text input in iframe") + .Value, + Is.EqualTo("filled in")); + } + + [Test] + public void Native_example() + { + var button = (IElementHandle) Browser.FindButton("clickMeTest") + .Native; + button.ClickAsync().Sync(); + Assert.That(Browser.FindButton("clickMeTest") + .Value, + Is.EqualTo("Click me - clicked")); + } + + [Test] + public void Title_example() + { + Assert.That(Browser.Title, Is.EqualTo("Coypu interaction tests page")); + } + + [Test] + public void TryUntil_example() + { + var tryThisButton = Browser.FindButton("try this"); + Assert.That(tryThisButton.Exists()); + Browser.TryUntil(() => tryThisButton.Click(), + () => Browser.HasContent("try until 5"), + TimeSpan.FromMilliseconds(50), + new Options {Timeout = TimeSpan.FromMilliseconds(10000)}); + } + } +} diff --git a/src/Coypu.AcceptanceTests/Examples/ClickExamples.cs b/src/Coypu.AcceptanceTests/Examples/ClickExamples.cs index 9d6e5ac6..1b584c90 100644 --- a/src/Coypu.AcceptanceTests/Examples/ClickExamples.cs +++ b/src/Coypu.AcceptanceTests/Examples/ClickExamples.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics; +using System.Threading; using NUnit.Framework; +using OpenQA.Selenium.DevTools.V85.Network; namespace Coypu.AcceptanceTests.Examples { @@ -48,19 +50,21 @@ public void ClickButton_WaitBeforeClick() [Test] public void ClickLink() { - Browser.ClickLink("Trigger a confirm"); - Browser.CancelModalDialog(); + Browser.CancelConfirm(() => { + Browser.ClickLink("Trigger a confirm"); + }); } [Test] public void ClickLink_WaitBeforeClick() { var stopWatch = Stopwatch.StartNew(); - - Browser.ClickLink("Trigger a confirm", _optionsWaitBeforeClick); - var actualWait = stopWatch.ElapsedMilliseconds; - Console.WriteLine($"\t-> Actual wait before click {actualWait} milliseconds"); - Browser.CancelModalDialog(); + long actualWait = 0; + Browser.CancelConfirm(() => { + Browser.ClickLink("Trigger a confirm", _optionsWaitBeforeClick); + actualWait = stopWatch.ElapsedMilliseconds; + Console.WriteLine($"\t-> Actual wait before click {actualWait} milliseconds"); + }); Assert.That(actualWait > WaitBeforeClickInSec.TotalMilliseconds, "\tDidn't wait enough!"); } @@ -68,8 +72,9 @@ public void ClickLink_WaitBeforeClick() [Test] public void ClickLink_WithTitle() { - Browser.ClickLink("Link with title"); - Browser.CancelModalDialog(); + Browser.CancelConfirm(() => { + Browser.ClickLink("Link with title"); + }); } [Test] @@ -85,4 +90,4 @@ public void Click_WaitBeforeClick() Assert.That(actualWait > WaitBeforeClickInSec.TotalMilliseconds, "\tDidn't wait enough!"); } } -} \ No newline at end of file +} diff --git a/src/Coypu.AcceptanceTests/Examples/CustomBrowserSession.cs b/src/Coypu.AcceptanceTests/Examples/CustomSeleniumBrowserSession.cs similarity index 86% rename from src/Coypu.AcceptanceTests/Examples/CustomBrowserSession.cs rename to src/Coypu.AcceptanceTests/Examples/CustomSeleniumBrowserSession.cs index b7d6ce1b..44faf932 100644 --- a/src/Coypu.AcceptanceTests/Examples/CustomBrowserSession.cs +++ b/src/Coypu.AcceptanceTests/Examples/CustomSeleniumBrowserSession.cs @@ -11,6 +11,12 @@ namespace Coypu.AcceptanceTests.Examples { + /// + /// This example shows how to use a custom Selenium WebDriver with Coypu. + /// There is no equivalent provided for Playwright as: + /// 1. A remote driver can be configured with an environment variable: https://playwright.dev/docs/selenium-grid + /// 2. Coypu.Browser.Native returns a Playwright BrowserContext which offers access to + /// whatever you might need to configure Playwright differently from the Coypu default internal class CustomBrowserSession { [TestCase("OS X 10.15", "safari", "13")] @@ -39,7 +45,7 @@ public void CustomBrowser(string browserName) { Assert.Inconclusive("This test requires Internet Explorer and will only run on Windows."); } - var driver = new SeleniumWebDriver(browser); + var driver = new SeleniumWebDriver(browser, false); using (var custom = new BrowserSession(driver)) { custom.Visit("https://saucelabs.com/test/guinea-pig"); @@ -86,4 +92,4 @@ private static RemoteWebDriver CustomWebDriver(ICapabilities capabilities) } } } -} \ No newline at end of file +} diff --git a/src/Coypu.AcceptanceTests/Examples/ModalDialogExamples.cs b/src/Coypu.AcceptanceTests/Examples/ModalDialogExamples.cs index 87ab5b81..b75061a1 100644 --- a/src/Coypu.AcceptanceTests/Examples/ModalDialogExamples.cs +++ b/src/Coypu.AcceptanceTests/Examples/ModalDialogExamples.cs @@ -7,18 +7,19 @@ public class ModalDialogExamples : WaitAndRetryExamples [Test] public void AcceptModalDialog_example() { - Browser.ClickLink("Trigger an alert"); - Assert.IsTrue(Browser.HasDialog("You have triggered an alert and this is the text.")); - - Browser.AcceptModalDialog(); - Assert.IsTrue(Browser.HasNoDialog("You have triggered an alert and this is the text.")); + Browser.AcceptAlert("You have triggered an alert and this is the text.", () => { + Browser.ClickLink("Trigger an alert"); + }); + Browser.FindLink("Trigger an alert - accepted") + .Now(); } [Test] public void CancelModalDialog_example() { - Browser.ClickLink("Trigger a confirm"); - Browser.CancelModalDialog(); + Browser.CancelConfirm(() => { + Browser.ClickLink("Trigger a confirm"); + }); Browser.FindLink("Trigger a confirm - cancelled") .Now(); } @@ -27,10 +28,11 @@ public void CancelModalDialog_example() public void ModalDialog_while_multiple_windows_are_open() { Browser.ClickLink("Open pop up window"); - Browser.ClickLink("Trigger a confirm"); - Browser.CancelModalDialog(); + Browser.CancelConfirm(() => { + Browser.ClickLink("Trigger a confirm"); + }); Browser.FindLink("Trigger a confirm - cancelled") .Now(); } } -} \ No newline at end of file +} diff --git a/src/Coypu.AcceptanceTests/Examples/SelectFromExamples.cs b/src/Coypu.AcceptanceTests/Examples/SelectFromExamples.cs index 0d532c46..f008b1ea 100644 --- a/src/Coypu.AcceptanceTests/Examples/SelectFromExamples.cs +++ b/src/Coypu.AcceptanceTests/Examples/SelectFromExamples.cs @@ -1,4 +1,6 @@ -using NUnit.Framework; +using System.Threading.Tasks.Dataflow; +using Coypu.Drivers.Playwright; +using NUnit.Framework; namespace Coypu.AcceptanceTests.Examples { @@ -54,14 +56,17 @@ public void Select_WithOptions_From_WithOptions() [Test] public void SelectFrom_OptionGroup_Workaround() { - var textField = Browser.FindField("selectFieldWithOptionGroups"); - Assert.That(textField.SelectedOption, Is.EqualTo("Barbie")); + if (Browser.Driver is PlaywrightDriver) { + Assert.Ignore("Playwright cannot interact with optgroup like this :("); + } + var select = Browser.FindField("selectFieldWithOptionGroups"); + Assert.That(select.SelectedOption, Is.EqualTo("Barbie")); - var elem = Browser.FindId("Male"); - elem.SelectOption("value2"); + var group = Browser.FindId("Male", Options.Invisible); + group.SelectOption("value2", Options.Invisible); - textField = Browser.FindField("selectFieldWithOptionGroups - changed"); - Assert.That(textField.SelectedOption, Is.EqualTo("Brendon")); + select = Browser.FindField("selectFieldWithOptionGroups - changed"); + Assert.That(select.SelectedOption, Is.EqualTo("Brendon")); } } -} \ No newline at end of file +} diff --git a/src/Coypu.AcceptanceTests/Examples/WindowExamples.cs b/src/Coypu.AcceptanceTests/Examples/WindowExamples.cs index 6272b955..20b30cc4 100644 --- a/src/Coypu.AcceptanceTests/Examples/WindowExamples.cs +++ b/src/Coypu.AcceptanceTests/Examples/WindowExamples.cs @@ -1,24 +1,29 @@ using System.Threading; +using Coypu.Drivers.Playwright; using NUnit.Framework; namespace Coypu.AcceptanceTests.Examples { public class WindowExamples : WaitAndRetryExamples { - private object GetOuterHeight() + private int GetOuterHeight() { - return Browser.ExecuteScript("return window.outerHeight;"); + return int.Parse(Browser.ExecuteScript("return window.outerHeight;").ToString()); } - private object GetOuterWidth() + private int GetOuterWidth() { - return Browser.ExecuteScript("return window.outerWidth;"); + return int.Parse(Browser.ExecuteScript("return window.outerWidth;").ToString()); } [Test] public void MaximiseWindow() { - var availWidth = Browser.ExecuteScript("return window.screen.availWidth;"); + if (Browser.Driver is PlaywrightDriver) + { + Assert.Ignore("Playwright does not support window maximisation"); + } + var availWidth = int.Parse(Browser.ExecuteScript("return window.screen.availWidth;").ToString()); var initalWidth = GetOuterWidth(); Assert.That(initalWidth, Is.LessThan(availWidth)); @@ -29,9 +34,9 @@ public void MaximiseWindow() [Test] public void RefreshingWindow() { - var tickBeforeRefresh = (long) Browser.ExecuteScript("return window.SpecData.CurrentTick;"); + var tickBeforeRefresh = long.Parse(Browser.ExecuteScript("return window.SpecData.CurrentTick;").ToString()); Browser.Refresh(); - var tickAfterRefresh = (long) Browser.ExecuteScript("return window.SpecData.CurrentTick;"); + var tickAfterRefresh = long.Parse(Browser.ExecuteScript("return window.SpecData.CurrentTick;").ToString()); Assert.That(tickAfterRefresh - tickBeforeRefresh, Is.GreaterThan(0)); } @@ -55,8 +60,9 @@ public void WindowScoping_example() Assert.That(mainWindow.FindButton("scoped button", Options.First) .Id, Is.EqualTo("scope1ButtonId")); - mainWindow.ExecuteScript("setTimeout(function() {document.getElementById(\"openPopupLink\").click();}), 3000"); + mainWindow.ExecuteScript("setTimeout(function() {document.getElementById(\"openPopupLink\").click();}, 300)"); var popUp = mainWindow.FindWindow("Pop Up Window"); + Assert.That(popUp.FindButton("scoped button") .Id, Is.EqualTo("popUpButtonId")); @@ -66,4 +72,4 @@ public void WindowScoping_example() Is.EqualTo("scope1ButtonId")); } } -} \ No newline at end of file +} diff --git a/src/Coypu.AcceptanceTests/Location.cs b/src/Coypu.AcceptanceTests/Location.cs index 1b8cca25..7d68317b 100644 --- a/src/Coypu.AcceptanceTests/Location.cs +++ b/src/Coypu.AcceptanceTests/Location.cs @@ -1,75 +1,78 @@ -using System; -using System.IO; -using Coypu.AcceptanceTests.Sites; -using Coypu.Drivers; -using NUnit.Framework; -using OpenQA.Selenium.Remote; - -namespace Coypu.AcceptanceTests -{ - [TestFixture] - public class Location - { - [SetUp] - public void SetUp() - { - _site = new SelfHostedSite(); - var sessionConfiguration = new SessionConfiguration - { - Browser = Browser.Chrome, - Timeout = TimeSpan.FromMilliseconds(1000), - AppHost = "127.0.0.1", - Port = _site.BaseUri.Port - }; - _browser = new BrowserSession(sessionConfiguration); - _browser.Visit("/"); - } - - [TearDown] - public void TearDown() - { - _browser.Dispose(); - _site.Dispose(); - } - - private BrowserSession _browser; - private SelfHostedSite _site; - - private void ReloadTestPage() - { - _browser.Visit(PathHelper.GetPageHtmlPath("InteractionTestsPage.htm")); - } - - [Test] - public void Go_back_and_forward_in_history() - { - _browser.Visit("/"); - _browser.Visit("/auto_login"); - Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/auto_login"))); - - _browser.GoBack(); - Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/"))); - - _browser.GoForward(); - Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/auto_login"))); - } - - [Test] - public void It_exposes_the_current_page_location() - { - _browser.Visit("/"); - Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/"))); - - _browser.Visit("/auto_login"); - Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/auto_login"))); - } - - [Test] - public void It_exposes_the_location_of_an_iframe_scope() - { - ReloadTestPage(); - Assert.That(_browser.FindFrame("iframe1")["src"], - Does.Contain("iFrame1.htm")); - } - } -} \ No newline at end of file +using System; +using System.IO; +using Coypu.AcceptanceTests.Sites; +using Coypu.Drivers; +using Coypu.Drivers.Playwright; +using Microsoft.Playwright; +using NUnit.Framework; +using OpenQA.Selenium.Remote; + +namespace Coypu.AcceptanceTests +{ + [TestFixture] + public class Location + { + [SetUp] + public void SetUp() + { + _site = new SelfHostedSite(); + var sessionConfiguration = new SessionConfiguration + { + Driver = typeof(PlaywrightDriver), + Browser = Browser.Chrome, + Timeout = TimeSpan.FromMilliseconds(1000), + AppHost = "127.0.0.1", + Port = _site.BaseUri.Port + }; + _browser = new BrowserSession(sessionConfiguration); + _browser.Visit("/"); + } + + [TearDown] + public void TearDown() + { + _browser.Dispose(); + _site.Dispose(); + } + + private BrowserSession _browser; + private SelfHostedSite _site; + + private void ReloadTestPage() + { + _browser.Visit(PathHelper.GetPageHtmlPath("InteractionTestsPage.htm")); + } + + [Test] + public void Go_back_and_forward_in_history() + { + _browser.Visit("/"); + _browser.Visit("/auto_login"); + Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/auto_login"))); + + _browser.GoBack(); + Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/"))); + + _browser.GoForward(); + Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/auto_login"))); + } + + [Test] + public void It_exposes_the_current_page_location() + { + _browser.Visit("/"); + Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/"))); + + _browser.Visit("/auto_login"); + Assert.That(_browser.Location, Is.EqualTo(new Uri(_site.BaseUri, "/auto_login"))); + } + + [Test] + public void It_exposes_the_location_of_an_iframe_scope() + { + ReloadTestPage(); + Assert.That(_browser.FindFrame("iframe1")["src"], + Does.Contain("iFrame1.htm")); + } + } +} diff --git a/src/Coypu.AcceptanceTests/StaleScopeExamples.cs b/src/Coypu.AcceptanceTests/StaleScopeExamples.cs index 71c8e913..fe820565 100644 --- a/src/Coypu.AcceptanceTests/StaleScopeExamples.cs +++ b/src/Coypu.AcceptanceTests/StaleScopeExamples.cs @@ -1,175 +1,176 @@ -using System; -using System.IO; -using System.Linq; -using Coypu.Drivers; -using Coypu.Drivers.Selenium; -using NUnit.Framework; - -namespace Coypu.AcceptanceTests -{ - /// - /// Simple examples for each API method - to show usage and check everything is wired up properly - /// - [TestFixture] - public class StaleScopeExamples - { - private BrowserSession browser; - - [OneTimeSetUp] - public void SetUpFixture() - { - var configuration = new SessionConfiguration - { - Timeout = TimeSpan.FromMilliseconds(2000), - Driver = typeof(SeleniumWebDriver), - Browser = Browser.Chrome - }; - browser = new BrowserSession(configuration); - - } - - [OneTimeTearDown] - public void TearDown() - { - browser.Dispose(); - } - - - private void VisitTestPage(string page) - { - browser.Visit(PathHelper.GetPageHtmlPath(page)); - } - - [Test] - public void Scope_becomes_stale() - { - VisitTestPage("tricky.htm"); - - var section1 = browser.FindSection("section 1"); - Assert.That(section1.FindLink("the link").Exists()); - - var originalLocation = browser.Location; - - VisitTestPage("iFrame1.htm"); - - Assert.That(section1.FindLink("the link").Missing()); - - browser.ExecuteScript("window.setTimeout(function() {window.location.href = '" + originalLocation + "'},1000);"); - - section1.ClickLink("the link"); - } - - - [Test] - public void Scope_becomes_stale_looking_for_all_xpath() - { - VisitTestPage("tricky.htm"); - - var section1 = browser.FindSection("section 1"); - Assert.That(section1.FindLink("the link").Exists()); - - VisitTestPage("iFrame1.htm"); - VisitTestPage("tricky.htm"); - Assert.That(section1.FindAllXPath("*").Count(), Is.GreaterThan(0)); - } - [Test] - public void Scope_becomes_stale_looking_for_all_css() - { - VisitTestPage("tricky.htm"); - - var section1 = browser.FindSection("section 1"); - Assert.That(section1.FindLink("the link").Exists()); - - VisitTestPage("iFrame1.htm"); - VisitTestPage("tricky.htm"); - Assert.That(section1.FindAllCss("*").Count(), Is.GreaterThan(0)); - } - - - [Test] - public void Scope_becomes_stale_iframe() - { - VisitTestPage("InteractionTestsPage.htm"); - - var originalLocation = browser.Location; - - var iframe1 = browser.FindFrame("iframe1"); - var iframe2 = browser.FindFrame("iframe2"); - var button = iframe1.FindButton("scoped button"); - - Assert.That(button.Exists()); - - Assert.That(iframe1.HasContent("I am iframe one")); - - VisitTestPage("tricky.htm"); - - Assert.That(iframe1.Missing()); - Assert.That(button.Missing()); - - browser.ExecuteScript("window.setTimeout(function() {window.location.href = '" + originalLocation + "'},1000);"); - - Assert.That(iframe1.HasContent("I am iframe one")); - Assert.That(iframe2.HasContent("I am iframe two")); - - Assert.That(browser.Title, Is.EqualTo("Coypu interaction tests page")); - - button.Click(); - } - - [Test] - public void Scope_becomes_stale_window() - { - VisitTestPage("InteractionTestsPage.htm"); - - browser.ClickLink("Open pop up window"); - - var timeToClickManuallyInIE = new Options {Timeout = TimeSpan.FromSeconds(10)}; - - var popUp = browser.FindWindow("Pop Up Window"); - var button = popUp.FindButton("scoped button", timeToClickManuallyInIE); - - Assert.That(button.Exists()); - Assert.That(popUp.HasContent("I am a pop up window")); - - CloseWindow(popUp); - Assert.That(popUp.Missing(), "Expected popUp window to be missing"); - Assert.That(button.Missing(), "Expected button in popup to be missing"); - - browser.ClickLink("Open pop up window"); - - Assert.That(popUp.HasContent("I am a pop up window", timeToClickManuallyInIE)); - button.Click(); - } - - [Test] - public void Window_is_not_refound_unless_stale() - { - VisitTestPage("InteractionTestsPage.htm"); - browser.ClickLink("Open pop up window"); - - var popUp = browser.FindWindow("Pop Up Window"); - var popUpBody = popUp.FindCss("body"); - - popUp.ExecuteScript("document.title = 'Changed title';"); - Assert.That(browser.FindLink("Open pop up window").Exists()); - - Assert.That(popUp.HasContent("I am a pop up window")); - - var findPopUpAgain = browser.FindWindow("Pop Up Window"); - Assert.That(findPopUpAgain.Missing(), "Expected pop-up not to be found now title has changed"); - } - - - private static void CloseWindow(BrowserWindow popUp) - { - try - { - popUp.ExecuteScript("self.close();"); - } - catch (Exception InvalidCastException) - { - // IE permissions - } - } - } -} \ No newline at end of file +using System; +using System.IO; +using System.Linq; +using Coypu.Drivers; +using Coypu.Drivers.Playwright; +using Coypu.Drivers.Selenium; +using NUnit.Framework; + +namespace Coypu.AcceptanceTests +{ + /// + /// Simple examples for each API method - to show usage and check everything is wired up properly + /// + [TestFixture] + public class StaleScopeExamples + { + private BrowserSession browser; + + [OneTimeSetUp] + public void SetUpFixture() + { + var configuration = new SessionConfiguration + { + Timeout = TimeSpan.FromMilliseconds(2000), + Driver = typeof(PlaywrightDriver), + Browser = Browser.Chrome + }; + browser = new BrowserSession(configuration); + + } + + [OneTimeTearDown] + public void TearDown() + { + browser.Dispose(); + } + + + private void VisitTestPage(string page) + { + browser.Visit(PathHelper.GetPageHtmlPath(page)); + } + + [Test] + public void Scope_becomes_stale() + { + VisitTestPage("tricky.htm"); + + var section1 = browser.FindSection("section 1"); + Assert.That(section1.FindLink("the link").Exists()); + + var originalLocation = browser.Location; + + VisitTestPage("iFrame1.htm"); + + Assert.That(section1.FindLink("the link").Missing()); + + browser.ExecuteScript("window.setTimeout(function() {window.location.href = '" + originalLocation + "'},1000);"); + + section1.ClickLink("the link"); + } + + + [Test] + public void Scope_becomes_stale_looking_for_all_xpath() + { + VisitTestPage("tricky.htm"); + + var section1 = browser.FindSection("section 1"); + Assert.That(section1.FindLink("the link").Exists()); + + VisitTestPage("iFrame1.htm"); + VisitTestPage("tricky.htm"); + Assert.That(section1.FindAllXPath("*").Count(), Is.GreaterThan(0)); + } + [Test] + public void Scope_becomes_stale_looking_for_all_css() + { + VisitTestPage("tricky.htm"); + + var section1 = browser.FindSection("section 1"); + Assert.That(section1.FindLink("the link").Exists()); + + VisitTestPage("iFrame1.htm"); + VisitTestPage("tricky.htm"); + Assert.That(section1.FindAllCss("*").Count(), Is.GreaterThan(0)); + } + + + [Test] + public void Scope_becomes_stale_iframe() + { + VisitTestPage("InteractionTestsPage.htm"); + + var originalLocation = browser.Location; + + var iframe1 = browser.FindFrame("iframe1"); + var iframe2 = browser.FindFrame("iframe2"); + var button = iframe1.FindButton("scoped button"); + + Assert.That(button.Exists()); + + Assert.That(iframe1.HasContent("I am iframe one")); + + VisitTestPage("tricky.htm"); + + Assert.That(iframe1.Missing()); + Assert.That(button.Missing()); + + browser.ExecuteScript("window.setTimeout(function() {window.location.href = '" + originalLocation + "'},1000);"); + + Assert.That(iframe1.HasContent("I am iframe one")); + Assert.That(iframe2.HasContent("I am iframe two")); + + Assert.That(browser.Title, Is.EqualTo("Coypu interaction tests page")); + + button.Click(); + } + + [Test] + public void Scope_becomes_stale_window() + { + VisitTestPage("InteractionTestsPage.htm"); + + browser.ClickLink("Open pop up window"); + + var timeToClickManuallyInIE = new Options {Timeout = TimeSpan.FromSeconds(10)}; + + var popUp = browser.FindWindow("Pop Up Window"); + var button = popUp.FindButton("scoped button", timeToClickManuallyInIE); + + Assert.That(button.Exists()); + Assert.That(popUp.HasContent("I am a pop up window")); + + CloseWindow(popUp); + Assert.That(popUp.Missing(), "Expected popUp window to be missing"); + Assert.That(button.Missing(), "Expected button in popup to be missing"); + + browser.ClickLink("Open pop up window"); + + Assert.That(popUp.HasContent("I am a pop up window", timeToClickManuallyInIE)); + button.Click(); + } + + [Test] + public void Window_is_not_refound_unless_stale() + { + VisitTestPage("InteractionTestsPage.htm"); + browser.ClickLink("Open pop up window"); + + var popUp = browser.FindWindow("Pop Up Window"); + var popUpBody = popUp.FindCss("body"); + + popUp.ExecuteScript("document.title = 'Changed title';"); + Assert.That(browser.FindLink("Open pop up window").Exists()); + + Assert.That(popUp.HasContent("I am a pop up window")); + + var findPopUpAgain = browser.FindWindow("Pop Up Window"); + Assert.That(findPopUpAgain.Missing(), "Expected pop-up not to be found now title has changed"); + } + + + private static void CloseWindow(BrowserWindow popUp) + { + try + { + popUp.ExecuteScript("self.close();"); + } + catch (Exception InvalidCastException) + { + // IE permissions + } + } + } +} diff --git a/src/Coypu.AcceptanceTests/TextPrecisionAndMatch.cs b/src/Coypu.AcceptanceTests/TextPrecisionAndMatch.cs index 784215c3..84bb57f0 100644 --- a/src/Coypu.AcceptanceTests/TextPrecisionAndMatch.cs +++ b/src/Coypu.AcceptanceTests/TextPrecisionAndMatch.cs @@ -1,107 +1,105 @@ -using System; -using NUnit.Framework; - -namespace Coypu.AcceptanceTests -{ - [TestFixture] - public class TextPrecisionAndMatch - { - - protected BrowserSession browser; - - [OneTimeSetUp] - public void SetUpFixture() - { - if (!OperatingSystem.IsWindows()) - { - Assert.Inconclusive("This test requires Internet Explorer and will only run on Windows."); - } - - var configuration = new SessionConfiguration - { - Timeout = TimeSpan.FromMilliseconds(2000), - Browser = Drivers.Browser.InternetExplorer - }; - browser = new BrowserSession(configuration); - - } - [OneTimeTearDown] - public void TearDown() - { - browser?.Dispose(); - } - - [SetUp] - public void SetUp() - { - ReloadTestPage(); - } - - protected void ReloadTestPage() - { - var testPageLocation = PathHelper.GetPageHtmlPath("InteractionTestsPage.htm"); - browser.Visit(testPageLocation); - } - - [Test] - public void First_allows_ambiguous_results() - { - Assert.That(browser.FindField("Some for labeled", Options.FirstSubstring).Id, Is.EqualTo("forLabeledRadioFieldPartialMatchId")); - Assert.That(browser.FindField("someFieldNameThatAppearsTwice", Options.FirstExact).Id, Is.EqualTo("someFieldNameThatAppearsTwice_1")); - Assert.That(browser.FindField("someFieldNameThatAppearsTwice", Options.FirstPreferExact).Id, Is.EqualTo("someFieldNameThatAppearsTwice_1")); - Assert.That(browser.FindField("Some for labeled radio option", Options.FirstPreferExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); - } - - [Test] - public void Single_does_not_allow_ambiguous_results() - { - Assert.Throws(() => browser.FindField("Some for labeled", Options.SingleSubstring).Now()); - Assert.Throws(() => browser.FindField("someFieldNameThatAppearsTwice", Options.SingleExact).Now()); - Assert.Throws(() => browser.FindField("someFieldNameThatAppearsTwice", Options.SinglePreferExact).Now()); - Assert.Throws(() => browser.FindField("Some for labeled", Options.SinglePreferExact).Now()); - } - - [Test] - public void Exact_finds_only_exact_text_matches() - { - Assert.That(browser.FindField("Some for labeled radio option", Options.FirstExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); - Assert.Throws(() => browser.FindField("Some for labeled radio", Options.FirstExact).Now()); - } - - [Test] - public void Substring_finds_substring_text_matches() - { - Assert.That(browser.FindField("Some for labeled radio option", Options.FirstSubstring).Id, Is.EqualTo("forLabeledRadioFieldPartialMatchId")); - Assert.That(browser.FindField("Some for labeled", Options.FirstSubstring).Id, Is.EqualTo("forLabeledRadioFieldPartialMatchId")); - } - - // There is a race condition where these tests can fail occasionally - // - // If you have TextPrecision.PreferExact and the expected content appears after some async delay then the - // disambiguation strategy may have just failed to find an exact match when the content appears and so the first - // check to find anything is one for substring matches, whereas the desired behaviour is to prefer the Exact match. - // - // Depending on if you have Match.First or Match.Single you may see the wrong element, or an AmbiguousHtmlException - // - // There is a (default) 50ms retry interval, but no delay between checking exact and substring, which is why it is only - // seen occassionally. - // - // Could possibly recheck for an exact match after finding multiple substring matches rather than just picking the first - // but this would be a performance hit all over the place. - // - // Probably best just to encourage switching to TextPrecision.Exact where you hit this situation. - [Test] - public void PreferExact_finds_exact_matches_before_substring_matches() - { - Assert.That(browser.FindField("Some for labeled radio option", Options.FirstPreferExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); - Assert.That(browser.FindField("Some for labeled radio option", Options.SinglePreferExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); - } - - [Test] - public void PreferExact_finds_exact_match_select_option_before_substring_match() - { - browser.Select("one",Options.PreferExact).From("Ambiguous select options"); - Assert.That(browser.FindField("Ambiguous select options").SelectedOption, Is.EqualTo("one")); - } - } -} \ No newline at end of file +using System; +using Coypu.Drivers.Playwright; +using Microsoft.Playwright; +using NUnit.Framework; + +namespace Coypu.AcceptanceTests +{ + [TestFixture] + public class TextPrecisionAndMatch + { + + protected BrowserSession browser; + + [OneTimeSetUp] + public void SetUpFixture() + { + var configuration = new SessionConfiguration + { + Timeout = TimeSpan.FromMilliseconds(0), + Browser = Drivers.Browser.Chrome, + Driver = typeof(PlaywrightDriver) + }; + browser = new BrowserSession(configuration); + + } + [OneTimeTearDown] + public void TearDown() + { + browser?.Dispose(); + } + + [SetUp] + public void SetUp() + { + ReloadTestPage(); + } + + protected void ReloadTestPage() + { + var testPageLocation = PathHelper.GetPageHtmlPath("InteractionTestsPage.htm"); + browser.Visit(testPageLocation); + } + + [Test] + public void First_allows_ambiguous_results() + { + Assert.That(browser.FindField("Some for labeled", Options.FirstSubstring).Id, Is.EqualTo("forLabeledRadioFieldPartialMatchId")); + Assert.That(browser.FindField("someFieldNameThatAppearsTwice", Options.FirstExact).Id, Is.EqualTo("someFieldNameThatAppearsTwice_1")); + Assert.That(browser.FindField("someFieldNameThatAppearsTwice", Options.FirstPreferExact).Id, Is.EqualTo("someFieldNameThatAppearsTwice_1")); + Assert.That(browser.FindField("Some for labeled radio option", Options.FirstPreferExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); + } + + [Test] + public void Single_does_not_allow_ambiguous_results() + { + Assert.Throws(() => browser.FindField("Some for labeled", Options.SingleSubstring).Now()); + Assert.Throws(() => browser.FindField("someFieldNameThatAppearsTwice", Options.SingleExact).Now()); + Assert.Throws(() => browser.FindField("someFieldNameThatAppearsTwice", Options.SinglePreferExact).Now()); + Assert.Throws(() => browser.FindField("Some for labeled", Options.SinglePreferExact).Now()); + } + + [Test] + public void Exact_finds_only_exact_text_matches() + { + Assert.That(browser.FindField("Some for labeled radio option", Options.FirstExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); + Assert.Throws(() => browser.FindField("Some for labeled radio", Options.FirstExact).Now()); + } + + [Test] + public void Substring_finds_substring_text_matches() + { + Assert.That(browser.FindField("Some for labeled radio option", Options.FirstSubstring).Id, Is.EqualTo("forLabeledRadioFieldPartialMatchId")); + Assert.That(browser.FindField("Some for labeled", Options.FirstSubstring).Id, Is.EqualTo("forLabeledRadioFieldPartialMatchId")); + } + + // There is a race condition where these tests can fail occasionally + // + // If you have TextPrecision.PreferExact and the expected content appears after some async delay then the + // disambiguation strategy may have just failed to find an exact match when the content appears and so the first + // check to find anything is one for substring matches, whereas the desired behaviour is to prefer the Exact match. + // + // Depending on if you have Match.First or Match.Single you may see the wrong element, or an AmbiguousHtmlException + // + // There is a (default) 50ms retry interval, but no delay between checking exact and substring, which is why it is only + // seen occassionally. + // + // Could possibly recheck for an exact match after finding multiple substring matches rather than just picking the first + // but this would be a performance hit all over the place. + // + // Probably best just to encourage switching to TextPrecision.Exact where you hit this situation. + [Test] + public void PreferExact_finds_exact_matches_before_substring_matches() + { + Assert.That(browser.FindField("Some for labeled radio option", Options.FirstPreferExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); + Assert.That(browser.FindField("Some for labeled radio option", Options.SinglePreferExact).Id, Is.EqualTo("forLabeledRadioFieldExactMatchId")); + } + + [Test] + public void PreferExact_finds_exact_match_select_option_before_substring_match() + { + browser.Select("one",Options.PreferExact).From("Ambiguous select options"); + Assert.That(browser.FindField("Ambiguous select options").SelectedOption, Is.EqualTo("one")); + } + } +} diff --git a/src/Coypu.AcceptanceTests/WaitAndRetryExamples.cs b/src/Coypu.AcceptanceTests/WaitAndRetryExamples.cs index 5286cfbb..f2135975 100644 --- a/src/Coypu.AcceptanceTests/WaitAndRetryExamples.cs +++ b/src/Coypu.AcceptanceTests/WaitAndRetryExamples.cs @@ -1,64 +1,65 @@ -using System; -using System.IO; -using Coypu.Drivers.Selenium; -using NUnit.Framework; -using NUnit.Framework.Interfaces; - -namespace Coypu.AcceptanceTests -{ - public class WaitAndRetryExamples - { - protected BrowserSession Browser; - - [OneTimeSetUp] - public void SetUpFixture() - { - var configuration = new SessionConfiguration - { - Timeout = TimeSpan.FromMilliseconds(5000), - Browser = Drivers.Browser.Chrome, - Driver = typeof(SeleniumWebDriver) - }; - Browser = new BrowserSession(configuration); - } - - [OneTimeTearDown] - public void TearDown() - { - Browser.Dispose(); - } - - [SetUp] - public void SetUp() - { - ReloadTestPageWithDelay(); - } - - [TearDown] - public void TearDownOnFail() - { - if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed) return; - TearDown(); - SetUpFixture(); - } - - protected void ApplyAsyncDelay() - { - // Hide the HTML then bring back after a short delay to test robustness - Browser.ExecuteScript("window.holdIt = window.document.body.innerHTML;"); - Browser.ExecuteScript("window.document.body.innerHTML = '';"); - Browser.ExecuteScript("setTimeout(function() {document.body.innerHTML = window.holdIt},250)"); - } - - protected void ReloadTestPage() - { - Browser.Visit(PathHelper.GetPageHtmlPath("InteractionTestsPage.htm")); - } - - protected void ReloadTestPageWithDelay() - { - ReloadTestPage(); - ApplyAsyncDelay(); - } - } -} \ No newline at end of file +using System; +using System.IO; +using Coypu.Drivers.Playwright; +using Coypu.Drivers.Selenium; +using NUnit.Framework; +using NUnit.Framework.Interfaces; + +namespace Coypu.AcceptanceTests +{ + public class WaitAndRetryExamples + { + protected BrowserSession Browser; + + [OneTimeSetUp] + public void SetUpFixture() + { + var configuration = new SessionConfiguration + { + Browser = Drivers.Browser.Chrome, + Driver = typeof(PlaywrightDriver), + Headless = true + }; + Browser = new BrowserSession(configuration); + } + + [OneTimeTearDown] + public void TearDown() + { + Browser.Dispose(); + } + + [SetUp] + public void SetUp() + { + ReloadTestPageWithDelay(); + } + + [TearDown] + public void TearDownOnFail() + { + if (TestContext.CurrentContext.Result.Outcome.Status != TestStatus.Failed) return; + TearDown(); + SetUpFixture(); + } + + protected void ApplyAsyncDelay() + { + // Hide the HTML then bring back after a short delay to test robustness + Browser.ExecuteScript("window.holdIt = window.document.body.innerHTML;"); + Browser.ExecuteScript("window.document.body.innerHTML = '';"); + Browser.ExecuteScript("setTimeout(function() {document.body.innerHTML = window.holdIt},250)"); + } + + protected void ReloadTestPage() + { + Browser.Visit(PathHelper.GetPageHtmlPath("InteractionTestsPage.htm")); + } + + protected void ReloadTestPageWithDelay() + { + ReloadTestPage(); + ApplyAsyncDelay(); + } + } +} diff --git a/src/Coypu.AcceptanceTests/WebRequests.cs b/src/Coypu.AcceptanceTests/WebRequests.cs index d456ae33..02b7e17f 100644 --- a/src/Coypu.AcceptanceTests/WebRequests.cs +++ b/src/Coypu.AcceptanceTests/WebRequests.cs @@ -1,75 +1,75 @@ -using System; -using System.IO; -using System.Text; -using Coypu.AcceptanceTests.Sites; -using NUnit.Framework; - -namespace Coypu.AcceptanceTests -{ - [TestFixture] - public class WebRequests - { - private SelfHostedSite site; - private BrowserSession browser; - - [SetUp] - public void SetUp() - { - site = new SelfHostedSite(); - - var configuration = new SessionConfiguration(); - - configuration.Timeout = TimeSpan.FromMilliseconds(1000); - configuration.Port = site.BaseUri.Port; - - browser = new BrowserSession(configuration); - browser.Visit("/"); - } - - [TearDown] - public void TearDown() - { - browser.Dispose(); - site.Dispose(); - } - - [Test] - public void It_saves_a_resource_from_the_web() - { - var saveAs = TempFileName(); - var expectedResource = Encoding.Default.GetBytes("bdd"); - - browser.SaveWebResource("/resource/bdd", saveAs); - - Assert.That(File.ReadAllBytes(saveAs), Is.EqualTo(expectedResource)); - } - - [Test] - public void It_saves_a_restricted_file_from_a_site_you_are_logged_into() - { - var saveAs = TempFileName(); - var expectedResource = "bdd"; - - browser.SaveWebResource("/restricted_resource/bdd", saveAs); - Assert.That(File.ReadAllBytes(saveAs), Is.Not.EqualTo(expectedResource)); - - browser.Visit("/auto_login"); - - browser.SaveWebResource("/restricted_resource/bdd", saveAs); - Assert.That(File.ReadAllText(saveAs), Is.EqualTo(expectedResource)); - } - - private string TempFileName() - { - var saveAs = Path.GetTempFileName(); - Clean(saveAs); - return saveAs; - } - - private void Clean(string saveAs) - { - if (File.Exists(saveAs)) - File.Delete(saveAs); - } - } -} \ No newline at end of file +using System; +using System.IO; +using System.Text; +using System.Threading; +using Coypu.AcceptanceTests.Sites; +using NUnit.Framework; + +namespace Coypu.AcceptanceTests +{ + [TestFixture] + public class WebRequests + { + private SelfHostedSite site; + private BrowserSession browser; + + [SetUp] + public void SetUp() + { + site = new SelfHostedSite(); + + var configuration = new SessionConfiguration(); + + configuration.Timeout = TimeSpan.FromMilliseconds(1000); + configuration.Port = site.BaseUri.Port; + + browser = new BrowserSession(configuration); + browser.Visit("/"); + } + + [TearDown] + public void TearDown() + { + browser.Dispose(); + site.Dispose(); + } + + [Test] + public void It_saves_a_resource_from_the_web() + { + var saveAs = TempFileName(); + var expectedResource = Encoding.Default.GetBytes("bdd"); + + browser.SaveWebResource("/resource/bdd", saveAs); + + Assert.That(File.ReadAllBytes(saveAs), Is.EqualTo(expectedResource)); + } + + [Test] + public void It_saves_a_restricted_file_from_a_site_you_are_logged_into() + { + var saveAs = TempFileName(); + var expectedResource = "bdd"; + + browser.SaveWebResource("/restricted_resource/bdd", saveAs); + Assert.That(File.ReadAllBytes(saveAs), Is.Not.EqualTo(expectedResource)); + + browser.Visit("/auto_login"); + browser.SaveWebResource("/restricted_resource/bdd", saveAs); + Assert.That(File.ReadAllText(saveAs), Is.EqualTo(expectedResource)); + } + + private string TempFileName() + { + var saveAs = Path.GetTempFileName(); + Clean(saveAs); + return saveAs; + } + + private void Clean(string saveAs) + { + if (File.Exists(saveAs)) + File.Delete(saveAs); + } + } +} diff --git a/src/Coypu.AcceptanceTests/sites/SelfHostedSite.cs b/src/Coypu.AcceptanceTests/sites/SelfHostedSite.cs index 15a3f70f..cbb908f9 100644 --- a/src/Coypu.AcceptanceTests/sites/SelfHostedSite.cs +++ b/src/Coypu.AcceptanceTests/sites/SelfHostedSite.cs @@ -12,13 +12,13 @@ namespace Coypu.AcceptanceTests.Sites public class SelfHostedSite : IDisposable { private readonly WebApplication _app; - + public SelfHostedSite() { var builder = WebApplication.CreateBuilder(); - builder.WebHost.UseUrls($"https://{IPAddress.Loopback}:0"); + builder.WebHost.UseUrls($"http://{IPAddress.Loopback}:0"); _app = builder.Build(); - + _app.MapGet("/", () => Results.Content( diff --git a/src/Coypu.Drivers.Tests/DriverSpecs.cs b/src/Coypu.Drivers.Tests/DriverSpecs.cs index e3d9a374..792559b9 100644 --- a/src/Coypu.Drivers.Tests/DriverSpecs.cs +++ b/src/Coypu.Drivers.Tests/DriverSpecs.cs @@ -1,7 +1,7 @@ using System; using System.IO; using System.Text.RegularExpressions; -using Coypu.Drivers.Selenium; +using Coypu.Drivers.Playwright; using Coypu.Drivers.Tests; using Coypu.AcceptanceTests; using Coypu.AcceptanceTests.Sites; @@ -9,9 +9,9 @@ using Coypu.Tests.TestBuilders; using Coypu.Tests.TestDoubles; using NUnit.Framework; -using OpenQA.Selenium.Chrome; using ElementFinder = Coypu.Finders.ElementFinder; using FrameFinder = Coypu.Finders.FrameFinder; +using Coypu.Drivers.Selenium; [SetUpFixture] public class AssemblyTearDown @@ -38,16 +38,15 @@ public class DriverSpecs { private static IDriver _driver; private static DriverScope _root; + private static readonly bool Headless = false; private static readonly Browser Browser = Browser.Chrome; protected static readonly Options DefaultOptions = new Options(); - - private static readonly Type DriverType = typeof(SeleniumWebDriver); - + private static readonly Type DriverType = typeof(PlaywrightDriver); protected static readonly SessionConfiguration DefaultSessionConfiguration = new SessionConfiguration { Browser = Browser, Driver = DriverType, - TextPrecision = TextPrecision.Exact + TextPrecision = TextPrecision.Exact, }; protected static readonly DisambiguationStrategy DisambiguationStrategy = new ThrowsWhenMissingButNoDisambiguationStrategy(); @@ -92,7 +91,7 @@ private static void EnsureDriver() _driver.Dispose(); } - _driver = (IDriver) Activator.CreateInstance(DriverType, Browser); + _driver = (IDriver) Activator.CreateInstance(DriverType, Browser, Headless); _root = null; } diff --git a/src/Coypu.Drivers.Tests/When_accessing_attributes.cs b/src/Coypu.Drivers.Tests/When_accessing_attributes.cs index 6ef6e36d..cc89b28d 100644 --- a/src/Coypu.Drivers.Tests/When_accessing_attributes.cs +++ b/src/Coypu.Drivers.Tests/When_accessing_attributes.cs @@ -1,19 +1,18 @@ -using Coypu.Finders; -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_accessing_attributes : DriverSpecs - { - [Test] - public void Exposes_element_attributes() - { - var formWithAttributesToTest = Id("attributeTestForm", Root, DefaultOptions); - formWithAttributesToTest["id"].ShouldBe("attributeTestForm"); - formWithAttributesToTest["method"].ShouldBe("post"); - formWithAttributesToTest["action"].ShouldBe("http://somesite.com/action.htm"); - formWithAttributesToTest["target"].ShouldBe("_parent"); - } - } -} \ No newline at end of file +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_accessing_attributes : DriverSpecs + { + [Test] + public void Exposes_element_attributes() + { + var formWithAttributesToTest = Id("attributeTestForm", Root, DefaultOptions); + formWithAttributesToTest["id"].ShouldBe("attributeTestForm"); + formWithAttributesToTest["method"].ShouldBe("post"); + formWithAttributesToTest["action"].ShouldBe("http://somesite.com/action.htm"); + formWithAttributesToTest["target"].ShouldBe("_parent"); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_clearing_cookies.cs b/src/Coypu.Drivers.Tests/When_clearing_cookies.cs deleted file mode 100644 index 9ae98ca3..00000000 --- a/src/Coypu.Drivers.Tests/When_clearing_cookies.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Linq; -using NUnit.Framework; -using OpenQA.Selenium; - -namespace Coypu.Drivers.Tests -{ - internal class When_clearing_cookies : DriverSpecs - { - [SetUp] - public void SetUpCookies() - { - Driver.Visit(TestSiteUrl("/"), Root); - } - - [Test] - public void Delete_all_the_session_cookies() - { - Driver.Cookies.AddCookie(new Cookie("cookie1", "value1")); - Driver.Cookies.AddCookie(new Cookie("cookie2", "value2")); - - var cookies = Driver.Cookies.GetAll() - .ToArray(); - - Assert.That(cookies.First(c => c.Name == "cookie1") - .Value, - Is.EqualTo("value1")); - Assert.That(cookies.First(c => c.Name == "cookie2") - .Value, - Is.EqualTo("value2")); - - Driver.Cookies.DeleteAll(); - - Assert.AreEqual(0, - Driver.Cookies.GetAll() - .Count()); - } - - [Test] - public void Delete_cookie() - { - var specialCookie = new Cookie("specialCookie", "specialValue"); - Driver.Cookies.AddCookie(specialCookie); - Driver.Cookies.AddCookie(new Cookie("cookie2", "value2")); - - var cookieCount = Driver.Cookies.GetAll() - .Count(); - var expectedCookieCount = cookieCount - 1; - - Driver.Cookies.DeleteCookie(specialCookie); - var cookies = Driver.Cookies.GetAll() - .ToArray(); - - Assert.That(cookies, Does.Not.Contain(cookies.Any(c => c.Name == "specialCookie"))); - Assert.That(cookies.First(c => c.Name == "cookie2") - .Value, - Is.EqualTo("value2")); - Assert.AreEqual(expectedCookieCount, - Driver.Cookies.GetAll() - .Count()); - } - - [Test] - public void Delete_cookie_by_name() - { - Driver.Cookies.AddCookie(new Cookie("cookie1", "value1")); - Driver.Cookies.AddCookie(new Cookie("cookie2", "value2")); - var cookieCount = Driver.Cookies.GetAll() - .Count(); - var expectedCookieCount = cookieCount - 1; - - Driver.Cookies.DeleteCookieNamed("cookie1"); - var cookies = Driver.Cookies.GetAll() - .ToArray(); - - Assert.That(cookies, Does.Not.Contain(cookies.Any(c => c.Name == "cookie1"))); - Assert.That(cookies.First(c => c.Name == "cookie2") - .Value, - Is.EqualTo("value2")); - Assert.AreEqual(expectedCookieCount, - Driver.Cookies.GetAll() - .Count()); - } - } -} \ No newline at end of file diff --git a/src/Coypu.Drivers.Tests/When_executing_script.cs b/src/Coypu.Drivers.Tests/When_executing_script.cs index de7e3c80..a01b0e11 100644 --- a/src/Coypu.Drivers.Tests/When_executing_script.cs +++ b/src/Coypu.Drivers.Tests/When_executing_script.cs @@ -1,36 +1,35 @@ -using Coypu.Finders; -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_executing_script : DriverSpecs - { - [Test] - public void Runs_the_script_in_the_browser() - { - Button("firstButtonId").Text.ShouldBe("first button"); - - Driver.ExecuteScript("document.getElementById('firstButtonId').innerHTML = 'script executed';", Root); - - Button("firstButtonId").Text.ShouldBe("script executed"); - } - - [Test] - public void Passes_the_arguments_to_the_browser() - { - Button("firstButtonId").Text.ShouldBe("first button"); - - Driver.ExecuteScript ("arguments[0].innerHTML = 'script executed ' + arguments[1];", Root, Button("firstButtonId"), 5); - - Button("firstButtonId").Text.ShouldBe("script executed 5"); - } - - [Test] - public void Returns_the_result() - - { - Driver.ExecuteScript("return document.getElementById('firstButtonId').innerHTML;", Root).ShouldBe("first button"); - } - } -} \ No newline at end of file +using Coypu.Finders; +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_executing_script : DriverSpecs + { + [Test] + public void Runs_the_script_in_the_browser() + { + Button("firstButtonId").Text.ShouldBe("first button"); + + Driver.ExecuteScript("document.getElementById('firstButtonId').innerHTML = 'script executed';", Root); + + Button("firstButtonId").Text.ShouldBe("script executed"); + } + + [Test] + public void Passes_the_arguments_to_the_browser() + { + Button("firstButtonId").Text.ShouldBe("first button"); + + Driver.ExecuteScript("arguments[0].innerHTML = 'script executed ' + arguments[1];", Root, Button("firstButtonId"), 5); + + Button("firstButtonId").Text.ShouldBe("script executed 5"); + } + + [Test] + public void Returns_the_result() + { + Driver.ExecuteScript("return document.getElementById('firstButtonId').innerHTML;", Root).ShouldBe("first button"); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_finding_fieldsets.cs b/src/Coypu.Drivers.Tests/When_finding_fieldsets.cs index c60e03ea..d206cfa0 100644 --- a/src/Coypu.Drivers.Tests/When_finding_fieldsets.cs +++ b/src/Coypu.Drivers.Tests/When_finding_fieldsets.cs @@ -1,29 +1,29 @@ -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_finding_fieldsets : DriverSpecs - { - [Test] - public void Finds_by_legend_text() - { - Fieldset("Scope 1").Id.ShouldBe("fieldsetScope1"); - Fieldset("Scope 2").Id.ShouldBe("fieldsetScope2"); - } - - [Test] - public void Finds_by_id() - { - Fieldset("fieldsetScope1").Native.ShouldBe(Fieldset("Scope 1").Native); - Fieldset("fieldsetScope2").Native.ShouldBe(Fieldset("Scope 2").Native); - } - - [Test] - public void Finds_only_fieldsets() - { - Assert.Throws(() => Fieldset("scope1TextInputFieldId")); - Assert.Throws(() => Fieldset("sectionOne")); - } - } -} \ No newline at end of file +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_finding_fieldsets : DriverSpecs + { + [Test] + public void Finds_by_legend_text() + { + Fieldset("Scope 1").Id.ShouldBe("fieldsetScope1"); + Fieldset("Scope 2").Id.ShouldBe("fieldsetScope2"); + } + + [Test] + public void Finds_by_id() + { + Fieldset("fieldsetScope1").Id.ShouldBe("fieldsetScope1"); + Fieldset("fieldsetScope2").Id.ShouldBe("fieldsetScope2"); + } + + [Test] + public void Finds_only_fieldsets() + { + Assert.Throws(() => Fieldset("scope1TextInputFieldId")); + Assert.Throws(() => Fieldset("sectionOne")); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_finding_iframes.cs b/src/Coypu.Drivers.Tests/When_finding_iframes.cs index ff631fd5..1817538d 100644 --- a/src/Coypu.Drivers.Tests/When_finding_iframes.cs +++ b/src/Coypu.Drivers.Tests/When_finding_iframes.cs @@ -1,29 +1,29 @@ -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_finding_iframes : DriverSpecs - { - [Test] - public void Finds_by_header_text() - { - Frame("I am iframe one").Id.ShouldBe("iframe1"); - Frame("I am iframe two").Id.ShouldBe("iframe2"); - } - - [Test] - public void Finds_by_id() - { - Frame("iframe1").Id.ShouldBe("iframe1"); - Frame("iframe2").Id.ShouldBe("iframe2"); - } - - [Test] - public void Finds_by_title() - { - Frame("iframe one title").Id.ShouldBe("iframe1"); - Frame("iframe two title").Id.ShouldBe("iframe2"); - } - } -} \ No newline at end of file +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_finding_iframes : DriverSpecs + { + [Test] + public void Finds_by_header_text() + { + Frame("I am iframe one").Id.ShouldBe("iframe1"); + Frame("I am iframe two").Id.ShouldBe("iframe2"); + } + + [Test] + public void Finds_by_id() + { + Frame("iframe1").Id.ShouldBe("iframe1"); + Frame("iframe2").Id.ShouldBe("iframe2"); + } + + [Test] + public void Finds_by_title() + { + Frame("iframe one title").Id.ShouldBe("iframe1"); + Frame("iframe two title").Id.ShouldBe("iframe2"); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_finding_sections.cs b/src/Coypu.Drivers.Tests/When_finding_sections.cs index 7d2db7c9..7e93b1e9 100644 --- a/src/Coypu.Drivers.Tests/When_finding_sections.cs +++ b/src/Coypu.Drivers.Tests/When_finding_sections.cs @@ -1,51 +1,51 @@ -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_finding_sections : DriverSpecs - { - [Test] - public void Finds_by_h1_text() - { - Section("Section One h1").Id.ShouldBe("sectionOne"); - Section("Section Two h1").Id.ShouldBe("sectionTwo"); - } - - [Test] - public void Finds_by_h2_text() - { - Section("Section One h2").Id.ShouldBe("sectionOne"); - Section("Section Two h2").Id.ShouldBe("sectionTwo"); - } - - [Test] - public void Finds_by_h3_text() - { - Section("Section One h3").Id.ShouldBe("sectionOne"); - Section("Section Two h3").Id.ShouldBe("sectionTwo"); - } - - [Test] - public void Finds_by_h6_text() - { - Section("Section One h6").Id.ShouldBe("sectionOne"); - Section("Section Two h6").Id.ShouldBe("sectionTwo"); - } - - [Test] - public void Finds_section_by_id() - { - Section("sectionOne").Id.ShouldBe("sectionOne"); - Section("sectionTwo").Id.ShouldBe("sectionTwo"); - } - - - [Test] - public void Only_finds_div_and_section() - { - Assert.Throws(() => Section("scope1TextInputFieldId")); - Assert.Throws(() => Section("fieldsetScope2")); - } - } -} \ No newline at end of file +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_finding_sections : DriverSpecs + { + [Test] + public void Finds_by_h1_text() + { + Section("Section One h1").Id.ShouldBe("sectionOne"); + Section("Section Two h1").Id.ShouldBe("sectionTwo"); + } + + [Test] + public void Finds_by_h2_text() + { + Section("Section One h2").Id.ShouldBe("sectionOne"); + Section("Section Two h2").Id.ShouldBe("sectionTwo"); + } + + [Test] + public void Finds_by_h3_text() + { + Section("Section One h3").Id.ShouldBe("sectionOne"); + Section("Section Two h3").Id.ShouldBe("sectionTwo"); + } + + [Test] + public void Finds_by_h6_text() + { + Section("Section One h6").Id.ShouldBe("sectionOne"); + Section("Section Two h6").Id.ShouldBe("sectionTwo"); + } + + [Test] + public void Finds_section_by_id() + { + Section("sectionOne").Id.ShouldBe("sectionOne"); + Section("sectionTwo").Id.ShouldBe("sectionTwo"); + } + + + [Test] + public void Only_finds_div_and_section() + { + Assert.Throws(() => Section("scope1TextInputFieldId")); + Assert.Throws(() => Section("fieldsetScope2")); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_finding_sections_as_divs.cs b/src/Coypu.Drivers.Tests/When_finding_sections_as_divs.cs index aa7e7556..c172b682 100644 --- a/src/Coypu.Drivers.Tests/When_finding_sections_as_divs.cs +++ b/src/Coypu.Drivers.Tests/When_finding_sections_as_divs.cs @@ -1,52 +1,52 @@ -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_finding_sections_as_divs : DriverSpecs - { - [Test] - public void Finds_by_h1_text() - { - Section("Div Section One h1").Id.ShouldBe("divSectionOne"); - Section("Div Section Two h1").Id.ShouldBe("divSectionTwo"); - } - - [Test] - public void Finds_by_h2_text() - { - Section("Div Section One h2").Id.ShouldBe("divSectionOne"); - Section("Div Section Two h2").Id.ShouldBe("divSectionTwo"); - } - - [Test] - public void Finds_by_h3_text() - { - Section("Div Section One h3").Id.ShouldBe("divSectionOne"); - Section("Div Section Two h3").Id.ShouldBe("divSectionTwo"); - } - - [Test] - public void Finds_by_h6_text() - { - Section("Div Section One h6").Id.ShouldBe("divSectionOne"); - Section("Div Section Two h6").Id.ShouldBe("divSectionTwo"); - } - - - [Test] - public void Finds_by_h2_text_within_child_link() - { - Section("Div Section One h2 with link").Id.ShouldBe("divSectionOneWithLink"); - Section("Div Section Two h2 with link").Id.ShouldBe("divSectionTwoWithLink"); - } - - - [Test] - public void Finds_by_div_by_id() - { - Section("divSectionOne").Native.ShouldBe(Section("Div Section One h1").Native); - Section("divSectionTwo").Native.ShouldBe(Section("Div Section Two h1").Native); - } - } -} \ No newline at end of file +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_finding_sections_as_divs : DriverSpecs + { + [Test] + public void Finds_by_h1_text() + { + Section("Div Section One h1").Id.ShouldBe("divSectionOne"); + Section("Div Section Two h1").Id.ShouldBe("divSectionTwo"); + } + + [Test] + public void Finds_by_h2_text() + { + Section("Div Section One h2").Id.ShouldBe("divSectionOne"); + Section("Div Section Two h2").Id.ShouldBe("divSectionTwo"); + } + + [Test] + public void Finds_by_h3_text() + { + Section("Div Section One h3").Id.ShouldBe("divSectionOne"); + Section("Div Section Two h3").Id.ShouldBe("divSectionTwo"); + } + + [Test] + public void Finds_by_h6_text() + { + Section("Div Section One h6").Id.ShouldBe("divSectionOne"); + Section("Div Section Two h6").Id.ShouldBe("divSectionTwo"); + } + + + [Test] + public void Finds_by_h2_text_within_child_link() + { + Section("Div Section One h2 with link").Id.ShouldBe("divSectionOneWithLink"); + Section("Div Section Two h2 with link").Id.ShouldBe("divSectionTwoWithLink"); + } + + + [Test] + public void Finds_by_div_by_id() + { + Section("divSectionOne").Text.ShouldBe(Section("Div Section One h1").Text); + Section("divSectionTwo").Text.ShouldBe(Section("Div Section Two h1").Text); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_finding_windows.cs b/src/Coypu.Drivers.Tests/When_finding_windows.cs index 7bd7d917..270d51bc 100644 --- a/src/Coypu.Drivers.Tests/When_finding_windows.cs +++ b/src/Coypu.Drivers.Tests/When_finding_windows.cs @@ -1,130 +1,140 @@ -using Coypu.Finders; -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_finding_windows : DriverSpecs - { - [Test] - public void Finds_by_name() - { - using (Driver) - { - OpenPopup(); - var window = Window("popUpWindowName", Root, DefaultOptions); - - window.Text.ShouldContain("I am a pop up window"); - - FindPopUpLink(); - } - } - - private static void OpenPopup() - { - Driver.Click(FindPopUpLink()); - } - - private static void OpenPopup2() - { - Driver.Click(FindPopUp2Link()); - } - - private static Element FindPopUpLink() - { - return Link("Open pop up window", Root, DefaultOptions); - } - - private static Element FindPopUp2Link() - { - return Link("Open pop up window 2", Root, DefaultOptions); - } - - private static Element FindPopUp() - { - return FindWindow("Pop Up Window"); - } - - [Test] - public void Finds_by_title() - { - using (Driver) - { - OpenPopup(); - FindPopUp().Text.ShouldContain("I am a pop up window"); - - FindPopUpLink(); - } - } - - [Test] - public void Finds_by_substring_title() - { - using (Driver) - { - OpenPopup2(); - FindPopUp().Text.ShouldContain("I am a pop up window 2"); - FindPopUp2Link(); - } - } - - [Test] - public void Finds_by_exact_title_over_substring() - { - using (Driver) - { - OpenPopup(); - OpenPopup2(); - FindPopUp().Text.ShouldContain("I am a pop up window"); - - FindPopUpLink(); - } - } - - [Test] - public void Finds_scoped_by_window() - { - using (Driver) - { - - OpenPopup(); - - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), - Driver, null, null, null, DisambiguationStrategy); - - Id("popUpButtonId", popUp); - - FindPopUpLink(); - } - } - - [Test] - public void Errors_on_no_such_window() - { - using (Driver) - { - OpenPopup(); - Assert.Throws(() => FindWindow("Not A Window")); - } - } - - private static Element FindWindow(string locator) - { - return Window(locator, Root, DefaultOptions); - } - - [Test] - public void Errors_on_window_closed() - { - using (Driver) - { - OpenPopup(); - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), - Driver, null, null, null, DisambiguationStrategy); - - Driver.ExecuteScript("self.close();", popUp); - Assert.Throws(() => FindPopUp()); - } - } - } -} \ No newline at end of file +using Coypu.Finders; +using Shouldly; +using NUnit.Framework; +using Coypu.Drivers.Playwright; +using Coypu.Timing; +using Coypu.Actions; +using System; +using System.Threading; + +namespace Coypu.Drivers.Tests +{ + internal class When_finding_windows : DriverSpecs + { + [Test] + public void Finds_by_name() + { + using (Driver) + { + if (Driver is PlaywrightDriver) + { + Assert.Ignore("Playwright does not seem to support window names"); + } + OpenPopup(); + var window = Window("popUpWindowName", Root, DefaultOptions); + + window.Text.ShouldContain("I am a pop up window"); + + FindPopUpLink(); + } + } + + private static void OpenPopup() + { + Driver.Click(FindPopUpLink()); + } + + private static void OpenPopup2() + { + Driver.Click(FindPopUp2Link()); + } + + private static Element FindPopUpLink() + { + return Link("Open pop up window", Root, DefaultOptions); + } + + private static Element FindPopUp2Link() + { + return Link("Open pop up window 2", Root, DefaultOptions); + } + + private static Element FindPopUp() + { + return FindWindow("Pop Up Window"); + } + + [Test] + public void Finds_by_title() + { + using (Driver) + { + OpenPopup(); + RetryUntilTimeoutTimingStrategy.Retry(() => FindPopUp().Text.ShouldContain("I am a pop up window")); + FindPopUpLink(); + } + } + + [Test] + public void Finds_by_substring_title() + { + using (Driver) + { + OpenPopup2(); + RetryUntilTimeoutTimingStrategy.Retry(() => FindPopUp().Text.ShouldContain("I am a pop up window 2")); + + FindPopUp2Link(); + } + } + + [Test] + public void Finds_by_exact_title_over_substring() + { + using (Driver) + { + OpenPopup(); + OpenPopup2(); + RetryUntilTimeoutTimingStrategy.Retry(() => FindPopUp().Text.ShouldContain("I am a pop up window")); + FindPopUpLink(); + } + } + + [Test] + public void Finds_scoped_by_window() + { + using (Driver) + { + + OpenPopup(); + + var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), + Driver, null, null, null, DisambiguationStrategy); + RetryUntilTimeoutTimingStrategy.Retry(() => popUp.Now()); + Id("popUpButtonId", popUp); + FindPopUpLink(); + } + } + + [Test] + public void Errors_on_no_such_window() + { + using (Driver) + { + OpenPopup(); + Assert.Throws(() => FindWindow("Not A Window")); + } + } + + private static Element FindWindow(string locator) + { + return Window(locator, Root, DefaultOptions); + } + + [Test] + public void Errors_on_window_closed() + { + using (Driver) + { + OpenPopup(); + var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), + Driver, null, null, null, DisambiguationStrategy); + + RetryUntilTimeoutTimingStrategy.Retry(() => popUp.Now()); + // Playwright errors before returning from ExecuteScript if the window is closed synchronously + Driver.ExecuteScript("window.setTimeout(() => self.close(), 1);", popUp); + Thread.Sleep(10); + Assert.Throws(() => FindPopUp()); + } + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_inspecting_dialog_text.cs b/src/Coypu.Drivers.Tests/When_inspecting_dialog_text.cs index 402edb5b..d8f9e784 100644 --- a/src/Coypu.Drivers.Tests/When_inspecting_dialog_text.cs +++ b/src/Coypu.Drivers.Tests/When_inspecting_dialog_text.cs @@ -1,28 +1,37 @@ -using Shouldly; -using NUnit.Framework; -namespace Coypu.Drivers.Tests -{ - internal class When_inspecting_dialog_text : DriverSpecs - { - [Test] - public void Finds_exact_text_in_alert() - { - using (Driver) - { - Driver.Click(Link("Trigger an alert")); - Driver.HasDialog("You have triggered an alert and this is the text.", Root).ShouldBeTrue(); - Driver.HasDialog("You have triggered a different alert and this is the different text.", Root).ShouldBeFalse(); - } - } - [Test] - public void Finds_exact_text_in_confirm() - { - using (Driver) - { - Driver.Click(Link("Trigger a confirm")); - Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeTrue(); - Driver.HasDialog("You have triggered a different confirm and this is the different text.", Root).ShouldBeFalse(); - } - } - } -} \ No newline at end of file +using Shouldly; +using NUnit.Framework; +using Coypu.Drivers.Playwright; +namespace Coypu.Drivers.Tests +{ + internal class When_inspecting_dialog_text : DriverSpecs + { + [Test] + public void Finds_exact_text_in_alert() + { + if (Driver is PlaywrightDriver) + { + Assert.Ignore("Playwright does not support the obsolete HasDialog API"); + } + using (Driver) + { + Driver.Click(Link("Trigger an alert")); + Driver.HasDialog("You have triggered an alert and this is the text.", Root).ShouldBeTrue(); + Driver.HasDialog("You have triggered a different alert and this is the different text.", Root).ShouldBeFalse(); + } + } + [Test] + public void Finds_exact_text_in_confirm() + { + if (Driver is PlaywrightDriver) + { + Assert.Ignore("Playwright does not support the obsolete HasDialog API"); + } + using (Driver) + { + Driver.Click(Link("Trigger a confirm")); + Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeTrue(); + Driver.HasDialog("You have triggered a different confirm and this is the different text.", Root).ShouldBeFalse(); + } + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_inspecting_location.cs b/src/Coypu.Drivers.Tests/When_inspecting_location.cs index 09250b88..01a324c8 100644 --- a/src/Coypu.Drivers.Tests/When_inspecting_location.cs +++ b/src/Coypu.Drivers.Tests/When_inspecting_location.cs @@ -1,43 +1,45 @@ -using System; -using System.Threading; -using Coypu.Finders; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_navigating : DriverSpecs - { - [Test] - public void Gets_the_current_browser_location() - { - Driver.Visit(TestSiteUrl("/"), Root); - Assert.That(Driver.Location(Root), Is.EqualTo(new Uri(TestSiteUrl("/")))); - - Driver.Visit(TestSiteUrl("/auto_login"), Root); - Assert.That(Driver.Location(Root), Is.EqualTo(new Uri(TestSiteUrl("/auto_login")))); - } - - [Test] - public void Gets_location_for_correct_window_scope() - { - Driver.Click(Link("Open pop up window")); - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), Driver, null, null, null, DisambiguationStrategy); - - Assert.That(Driver.Location(popUp) - .AbsoluteUri, - Does.EndWith("src/Coypu.Drivers.Tests/bin/Debug/net6.0/html/popup.htm")); - } - - [Test] - public void Not_just_when_set_by_visit() - { - Driver.Visit(TestSiteUrl("/auto_login"), Root); - Driver.ExecuteScript("document.location.href = '" + TestSiteUrl("/resource/bdd") + "'", Root); - - // Seems like WebDriver is not waiting on JS, has exec been made asnyc? - Thread.Sleep(500); - - Assert.That(Driver.Location(Root), Is.EqualTo(new Uri(TestSiteUrl("/resource/bdd")))); - } - } -} \ No newline at end of file +using System; +using System.Threading; +using Coypu.Actions; +using Coypu.Finders; +using Coypu.Timing; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_navigating : DriverSpecs + { + [Test] + public void Gets_the_current_browser_location() + { + Driver.Visit(TestSiteUrl("/"), Root); + Assert.That(Driver.Location(Root), Is.EqualTo(new Uri(TestSiteUrl("/")))); + + Driver.Visit(TestSiteUrl("/auto_login"), Root); + Assert.That(Driver.Location(Root), Is.EqualTo(new Uri(TestSiteUrl("/auto_login")))); + } + + [Test] + public void Gets_location_for_correct_window_scope() + { + Driver.Click(Link("Open pop up window")); + var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), Driver, null, null, null, DisambiguationStrategy); + RetryUntilTimeoutTimingStrategy.Retry(() => popUp.Now()); + Assert.That(Driver.Location(popUp) + .AbsoluteUri, + Does.EndWith("src/Coypu.Drivers.Tests/bin/Debug/net6.0/html/popup.htm")); + } + + [Test] + public void Not_just_when_set_by_visit() + { + Driver.Visit(TestSiteUrl("/auto_login"), Root); + Driver.ExecuteScript("document.location.href = '" + TestSiteUrl("/resource/bdd") + "'", Root); + + // Seems like WebDriver is not waiting on JS, has exec been made asnyc? + Thread.Sleep(500); + + Assert.That(Driver.Location(Root), Is.EqualTo(new Uri(TestSiteUrl("/resource/bdd")))); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_interacting_with_dialogs.cs b/src/Coypu.Drivers.Tests/When_interacting_with_dialogs.cs deleted file mode 100644 index 982c6dac..00000000 --- a/src/Coypu.Drivers.Tests/When_interacting_with_dialogs.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using Coypu.Finders; -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_interacting_with_dialogs : DriverSpecs - { - [Test] - public void Accepts_alerts() - { - using (Driver) - { - Driver.Click(Link("Trigger an alert")); - Driver.HasDialog("You have triggered an alert and this is the text.", Root).ShouldBeTrue(); - Driver.AcceptModalDialog(Root); - Driver.HasDialog("You have triggered an alert and this is the text.", Root).ShouldBeFalse(); - } - } - - - [Test] - public void Clears_dialog() - { - using (Driver) - { - Driver.Click(Link("Trigger a confirm")); - Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeTrue(); - Driver.AcceptModalDialog(Root); - Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeFalse(); - } - } - - [Test] - public void Missing_dialog_throws_coypu_exception() - { - using (Driver) - { - Assert.Throws(() => Driver.AcceptModalDialog(Root)); - Assert.Throws(() => Driver.CancelModalDialog(Root)); - } - } - - [Test] - public void Returns_true() - { - using (Driver) - { - Driver.Click(Link("Trigger a confirm")); - Driver.AcceptModalDialog(Root); - Link("Trigger a confirm - accepted", Root).ShouldNotBeNull(); - } - } - - - [Test] - public void Cancel_Clears_dialog() - { - using (Driver) - { - Driver.Click(Link("Trigger a confirm")); - Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeTrue(); - Driver.CancelModalDialog(Root); - Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeFalse(); - } - } - - [Test] - public void Cancel_Returns_false() - { - using (Driver) - { - Driver.Click(Link("Trigger a confirm")); - Driver.CancelModalDialog(Root); - - Link("Trigger a confirm - cancelled"); - } - } - - // IE can't do this - [Test] - public void Finds_scope_first_for_alerts() - { - using (Driver) - { - Driver.Click(Link("Open pop up window")); - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), Driver, null, null, null, DisambiguationStrategy); - Assert.That(Driver.Title(popUp), Is.EqualTo("Pop Up Window")); - - Driver.ExecuteScript("window.setTimeout(function() {document.getElementById('alertTriggerLink').click();},200);", Root); - Assert.That(Driver.Title(popUp), Is.EqualTo("Pop Up Window")); - - System.Threading.Thread.Sleep(1000); - Driver.AcceptModalDialog(Root); - Driver.HasDialog("You have triggered a alert and this is the text.", Root).ShouldBeFalse(); - } - } - - // IE can't do this - [Test] - public void Finds_scope_first_for_confirms() - { - using (Driver) - { - Driver.Click(Link("Open pop up window")); - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), Driver, null, null, null, DisambiguationStrategy); - Assert.That(Driver.Title(popUp), Is.EqualTo("Pop Up Window")); - - Driver.ExecuteScript("window.setTimeout(function() {document.getElementById('confirmTriggerLink').click();},500);", Root); - Assert.That(Driver.Title(popUp), Is.EqualTo("Pop Up Window")); - CloseWindow(popUp); - - System.Threading.Thread.Sleep(500); - Driver.CancelModalDialog(Root); - Driver.HasDialog("You have triggered a confirm and this is the text.", Root).ShouldBeFalse(); - } - } - - private static void CloseWindow(DriverScope popUp) - { - try - { - Driver.ExecuteScript("self.close();", popUp); - } - catch (Exception InvalidCastException) - { - // IE permissions - } - } - } -} \ No newline at end of file diff --git a/src/Coypu.Drivers.Tests/When_refreshing_windows.cs b/src/Coypu.Drivers.Tests/When_refreshing_windows.cs index bc4060ef..bc396efd 100644 --- a/src/Coypu.Drivers.Tests/When_refreshing_windows.cs +++ b/src/Coypu.Drivers.Tests/When_refreshing_windows.cs @@ -1,5 +1,6 @@ using System; using Coypu.Finders; +using Coypu.Timing; using NUnit.Framework; namespace Coypu.Drivers.Tests @@ -17,26 +18,19 @@ public void RefreshesCorrectWindowScope() { Driver.Click(Link("Open pop up window")); var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver,"Pop Up Window",Root,DefaultOptions), Driver, null, null, null, DisambiguationStrategy); - - try - { - RefreshCausesScopeToReload(popUp); - } - finally - { - Driver.ExecuteScript("return self.close();", popUp); - } + RetryUntilTimeoutTimingStrategy.Retry(() => popUp.Now()); + RefreshCausesScopeToReload(popUp); } private static void RefreshCausesScopeToReload(DriverScope driverScope) { - var tickBeforeRefresh = (Int64) Driver.ExecuteScript("return window.SpecData.CurrentTick;", driverScope); + var tickBeforeRefresh = long.Parse(Driver.ExecuteScript("return window.SpecData.CurrentTick;", driverScope).ToString()); Driver.Refresh(driverScope); - var tickAfterRefresh = (Int64) Driver.ExecuteScript("return window.SpecData.CurrentTick;", driverScope); + var tickAfterRefresh = long.Parse(Driver.ExecuteScript("return window.SpecData.CurrentTick;", driverScope).ToString()); Assert.That(tickAfterRefresh, Is.GreaterThan(tickBeforeRefresh)); } } -} \ No newline at end of file +} diff --git a/src/Coypu.Drivers.Tests/When_selecting_options.cs b/src/Coypu.Drivers.Tests/When_selecting_options.cs index 384be2c3..74767eca 100644 --- a/src/Coypu.Drivers.Tests/When_selecting_options.cs +++ b/src/Coypu.Drivers.Tests/When_selecting_options.cs @@ -1,55 +1,56 @@ -using Coypu.Finders; -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_selecting_options : DriverSpecs - { - private static DriverScope GetSelectScope(string locator) - { - var select = new BrowserWindow(DefaultSessionConfiguration, - new SelectFinder(Driver, locator, Root, DefaultOptions), Driver, - null, null, null, DisambiguationStrategy); - return @select; - } - - [Test] - public void Sets_text_of_selected_option() - { - Field("containerLabeledSelectFieldId").SelectedOption.ShouldBe("select two option one"); - - Driver.Click(FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions))); - - Field("containerLabeledSelectFieldId").SelectedOption.ShouldBe("select two option two"); - } - - [Test] - public void Selected_option_respects_TextPrecision() - { - Assert.That( - FindSingle(new OptionFinder(Driver, "select two option t", GetSelectScope("containerLabeledSelectFieldId"), Options.Substring)).Text, - Is.EqualTo("select two option two")); - - Assert.That( - FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), Options.Exact)).Text, - Is.EqualTo("select two option two")); - - Assert.Throws( - () => FindSingle(new OptionFinder(Driver, "select two option t", GetSelectScope("containerLabeledSelectFieldId"), Options.Exact))); - } - - [Test] - public void Selected_option_finds_exact_by_container_label() - { - Assert.That(FindSingle(new OptionFinder(Driver, "one", GetSelectScope("Ambiguous select options"), Options.Exact)).Text, Is.EqualTo("one")); - } - - [Test] - public void Selected_option_finds_substring_by_container_label() - { - Assert.That(FindSingle(new OptionFinder(Driver, "one", GetSelectScope("Ambiguous select options"), Options.Substring)).Text, Is.EqualTo("one")); - } - } - -} \ No newline at end of file +using Coypu.Finders; +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_selecting_options : DriverSpecs + { + private static DriverScope GetSelectScope(string locator) + { + var select = new BrowserWindow(DefaultSessionConfiguration, + new SelectFinder(Driver, locator, Root, DefaultOptions), Driver, + null, null, null, DisambiguationStrategy); + return @select; + } + + [Test] + public void Sets_text_of_selected_option() + { + Field("containerLabeledSelectFieldId").SelectedOption.ShouldBe("select two option one"); + + var option = FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions)); + Driver.SelectOption(GetSelectScope("containerLabeledSelectFieldId").Now(), option, "select two option two"); + + Field("containerLabeledSelectFieldId").SelectedOption.ShouldBe("select two option two"); + } + + [Test] + public void Selected_option_respects_TextPrecision() + { + Assert.That( + FindSingle(new OptionFinder(Driver, "select two option t", GetSelectScope("containerLabeledSelectFieldId"), Options.Substring)).Text, + Is.EqualTo("select two option two")); + + Assert.That( + FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), Options.Exact)).Text, + Is.EqualTo("select two option two")); + + Assert.Throws( + () => FindSingle(new OptionFinder(Driver, "select two option t", GetSelectScope("containerLabeledSelectFieldId"), Options.Exact))); + } + + [Test] + public void Selected_option_finds_exact_by_container_label() + { + Assert.That(FindSingle(new OptionFinder(Driver, "one", GetSelectScope("Ambiguous select options"), Options.Exact)).Text, Is.EqualTo("one")); + } + + [Test] + public void Selected_option_finds_substring_by_container_label() + { + Assert.That(FindSingle(new OptionFinder(Driver, "one", GetSelectScope("Ambiguous select options"), Options.Substring)).Text, Is.EqualTo("one")); + } + } + +} diff --git a/src/Coypu.Drivers.Tests/When_setting_fields.cs b/src/Coypu.Drivers.Tests/When_setting_fields.cs index ece193d2..d48e7037 100644 --- a/src/Coypu.Drivers.Tests/When_setting_fields.cs +++ b/src/Coypu.Drivers.Tests/When_setting_fields.cs @@ -1,108 +1,120 @@ -using Coypu.Finders; -using Shouldly; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_setting_fields : DriverSpecs - { - - private static DriverScope GetSelectScope(string locator) - { - var select = new BrowserWindow(DefaultSessionConfiguration, - new SelectFinder(Driver, locator, Root, DefaultOptions), Driver, - null, null, null, DisambiguationStrategy); - return @select; - } - - [Test] - public void Sets_value_of_text_input_field_with_id() - { - var textField = Field("containerLabeledTextInputFieldName"); - Driver.Set(textField, "should be much quicker since it's set by js"); - - textField.Value.ShouldBe("should be much quicker since it's set by js"); - - var findAgain = Field("containerLabeledTextInputFieldName"); - findAgain.Value.ShouldBe("should be much quicker since it's set by js"); - } - - [Test] - public void Sets_value_of_text_input_field_with_no_id() - { - var textField = Field("Field with no id"); - Driver.Set(textField, "set by sendkeys"); - - textField.Value.ShouldBe("set by sendkeys"); - - var findAgain = Field("Field with no id"); - findAgain.Value.ShouldBe("set by sendkeys"); - } - - [Test] - public void Sets_value_of_number_input_field() - { - var numberField = Field("containerLabeledNumberInputFieldId"); - Driver.Set(numberField, "5150"); - - numberField.Value.ShouldBe("5150"); - - var findAgain = Field("containerLabeledNumberInputFieldId"); - findAgain.Value.ShouldBe("5150"); - } - - [Test] - public void Sets_value_of_text_input_field_with_no_type() - { - var textField = Field("fieldWithNoType"); - Driver.Set(textField, "set by sendkeys"); - - textField.Value.ShouldBe("set by sendkeys"); - - var findAgain = Field("fieldWithNoType"); - findAgain.Value.ShouldBe("set by sendkeys"); - } - - - [Test] - public void Sets_value_of_textarea_field() - { - var textField = Field("containerLabeledTextareaFieldName"); - Driver.Set(textField, "New textarea value"); - - textField.Value.ShouldBe("New textarea value"); - - var findAgain = Field("containerLabeledTextareaFieldName"); - findAgain.Value.ShouldBe("New textarea value"); - } - - - [Test] - public void Selects_option_by_text_or_value() - { - var textField = Field("containerLabeledSelectFieldId"); - textField.Value.ShouldBe("select2value1"); - - Driver.Click(FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions))); - - var findAgain = Field("containerLabeledSelectFieldId"); - findAgain.Value.ShouldBe("select2value2"); - - Driver.Click(FindSingle(new OptionFinder(Driver, "select two option one", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions))); - - var andAgain = Field("containerLabeledSelectFieldId"); - andAgain.Value.ShouldBe("select2value1"); - } - - [Test] - public void Fires_change_event_when_selecting_an_option() - { - var textField = Field("containerLabeledSelectFieldId"); - textField.Name.ShouldBe("containerLabeledSelectFieldName"); - - Driver.Click(FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions))); - - Field("containerLabeledSelectFieldId", Root).Name.ShouldBe("containerLabeledSelectFieldName - changed"); - } - } -} \ No newline at end of file +using Coypu.Finders; +using Shouldly; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_setting_fields : DriverSpecs + { + + private static DriverScope GetSelectScope(string locator) + { + var select = new BrowserWindow(DefaultSessionConfiguration, + new SelectFinder(Driver, locator, Root, DefaultOptions), Driver, + null, null, null, DisambiguationStrategy); + return @select; + } + + [Test] + public void Sets_value_of_text_input_field_with_id() + { + var textField = Field("containerLabeledTextInputFieldName"); + Driver.Set(textField, "should be much quicker since it's set by js"); + + textField.Value.ShouldBe("should be much quicker since it's set by js"); + + var findAgain = Field("containerLabeledTextInputFieldName"); + findAgain.Value.ShouldBe("should be much quicker since it's set by js"); + } + + [Test] + public void Sets_value_of_text_input_field_with_no_id() + { + var textField = Field("Field with no id"); + Driver.Set(textField, "set by sendkeys"); + + textField.Value.ShouldBe("set by sendkeys"); + + var findAgain = Field("Field with no id"); + findAgain.Value.ShouldBe("set by sendkeys"); + } + + [Test] + public void Sets_value_of_number_input_field() + { + var numberField = Field("containerLabeledNumberInputFieldId"); + Driver.Set(numberField, "5150"); + + numberField.Value.ShouldBe("5150"); + + var findAgain = Field("containerLabeledNumberInputFieldId"); + findAgain.Value.ShouldBe("5150"); + } + + [Test] + public void Sets_value_of_text_input_field_with_no_type() + { + var textField = Field("fieldWithNoType"); + Driver.Set(textField, "set by sendkeys"); + + textField.Value.ShouldBe("set by sendkeys"); + + var findAgain = Field("fieldWithNoType"); + findAgain.Value.ShouldBe("set by sendkeys"); + } + + + [Test] + public void Sets_value_of_textarea_field() + { + var textField = Field("containerLabeledTextareaFieldName"); + Driver.Set(textField, "New textarea value"); + + textField.Value.ShouldBe("New textarea value"); + + var findAgain = Field("containerLabeledTextareaFieldName"); + findAgain.Value.ShouldBe("New textarea value"); + } + + + [Test] + public void Selects_option_by_text_or_value() + { + var textField = Field("containerLabeledSelectFieldId"); + textField.Value.ShouldBe("select2value1"); + + Driver.SelectOption( + GetSelectScope("containerLabeledSelectFieldId").Now(), + FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions)), + "select two option two" + ); + + var findAgain = Field("containerLabeledSelectFieldId"); + findAgain.Value.ShouldBe("select2value2"); + + Driver.SelectOption( + GetSelectScope("containerLabeledSelectFieldId").Now(), + FindSingle(new OptionFinder(Driver, "select two option one", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions)), + "select two option one" + ); + + var andAgain = Field("containerLabeledSelectFieldId"); + andAgain.Value.ShouldBe("select2value1"); + } + + [Test] + public void Fires_change_event_when_selecting_an_option() + { + var textField = Field("containerLabeledSelectFieldId"); + textField.Name.ShouldBe("containerLabeledSelectFieldName"); + + Driver.SelectOption( + GetSelectScope("containerLabeledSelectFieldId").Now(), + FindSingle(new OptionFinder(Driver, "select two option two", GetSelectScope("containerLabeledSelectFieldId"), DefaultOptions)), + "select two option two" + ); + + Field("containerLabeledSelectFieldId", Root).Name.ShouldBe("containerLabeledSelectFieldName - changed"); + } + } +} diff --git a/src/Coypu.Drivers.Tests/When_sizing_windows.cs b/src/Coypu.Drivers.Tests/When_sizing_windows.cs index c0c1d46a..9e2c182c 100644 --- a/src/Coypu.Drivers.Tests/When_sizing_windows.cs +++ b/src/Coypu.Drivers.Tests/When_sizing_windows.cs @@ -1,94 +1,98 @@ -using System.Drawing; -using Coypu.Finders; -using NUnit.Framework; - -namespace Coypu.Drivers.Tests -{ - internal class When_sizing_windows : DriverSpecs - { - [Test] - public void MaximisesWindow() - { - using (Driver) - { - AssertMaximisesWindow(Root); - } - } - - [Test] - public void MaximisesCorrectWindowScope() - { - using (Driver) - { - Driver.Click(Link("Open pop up window")); - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), - Driver, null, null, null, DisambiguationStrategy); - - try - { - AssertMaximisesWindow(popUp); - } - finally - { - Driver.ExecuteScript("return self.close();", popUp); - } - } - } - - private static void AssertMaximisesWindow(DriverScope driverScope) - { - var availWidth = Driver.ExecuteScript("return window.screen.availWidth;", driverScope); - var initalWidth = Driver.ExecuteScript("return window.outerWidth;", driverScope); - - Assert.That(initalWidth, Is.LessThan(availWidth)); - - Driver.MaximiseWindow(driverScope); - - Assert.That( Driver.ExecuteScript("return window.outerWidth;", driverScope), Is.GreaterThanOrEqualTo(availWidth)); - } - - - - [Test] - public void ResizesWindow() - { - using (Driver) - { - AssertResizesWindow(Root); - } - } - - [Test] - public void ResizesCorrectWindowScope() - { - using (Driver) - { - Driver.Click(Link("Open pop up window")); - var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), - Driver, null, null, null, DisambiguationStrategy); - - try - { - AssertResizesWindow(popUp); - } - finally - { - Driver.ExecuteScript("return self.close();", popUp); - } - } - } - - private static void AssertResizesWindow(DriverScope driverScope) - { - var availWidth = Driver.ExecuteScript("return window.screen.availWidth;", driverScope); - var initalWidth = Driver.ExecuteScript("return window.outerWidth;", driverScope); - - Assert.That(initalWidth, Is.LessThan(availWidth)); - - Driver.ResizeTo(new Size(768, 500), driverScope); - - Assert.That(Driver.ExecuteScript("return window.outerWidth;", driverScope), Is.EqualTo(768)); - Assert.That(Driver.ExecuteScript("return window.outerHeight;", driverScope), Is.EqualTo(500)); - } - } -} \ No newline at end of file +using System; +using System.Drawing; +using Coypu.Actions; +using Coypu.Drivers.Playwright; +using Coypu.Finders; +using Coypu.Timing; +using NUnit.Framework; + +namespace Coypu.Drivers.Tests +{ + internal class When_sizing_windows : DriverSpecs + { + [Test] + public void MaximisesWindow() + { + using (Driver) + { + AssertMaximisesWindow(Root); + } + } + + [Test] + public void MaximisesCorrectWindowScope() + { + using (Driver) + { + Driver.Click(Link("Open pop up window")); + var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), + Driver, null, null, null, DisambiguationStrategy); + RetryUntilTimeoutTimingStrategy.Retry(() => popUp.Now()); + try + { + AssertMaximisesWindow(popUp); + } + finally + { + Driver.ExecuteScript("window.setTimeout(() => self.close(), 1);", popUp); + } + } + } + + private static void AssertMaximisesWindow(DriverScope driverScope) + { + if (Driver is PlaywrightDriver) + { + Assert.Ignore("Playwright does not support window maximisation"); + } + Driver.ResizeTo(new Size(768, 400), driverScope); + Driver.MaximiseWindow(driverScope); + + Assert.That( Driver.ExecuteScript("return window.outerWidth;", driverScope), Is.GreaterThan(768)); + Assert.That( Driver.ExecuteScript("return window.outerHeight;", driverScope), Is.GreaterThan(400)); + } + + [Test] + public void ResizesWindow() + { + using (Driver) + { + AssertResizesWindow(Root); + } + } + + [Test] + public void ResizesCorrectWindowScope() + { + using (Driver) + { + Driver.Click(Link("Open pop up window")); + var popUp = new BrowserWindow(DefaultSessionConfiguration, new WindowFinder(Driver, "Pop Up Window", Root, DefaultOptions), + Driver, null, null, null, DisambiguationStrategy); + RetryUntilTimeoutTimingStrategy.Retry(() => popUp.Now()); + try + { + AssertResizesWindow(popUp); + } + finally + { + Driver.ExecuteScript("window.setTimeout(() => self.close(), 1);", popUp); + } + } + } + + private static void AssertResizesWindow(DriverScope driverScope) + { + var initalWidth = Driver.ExecuteScript("return window.outerWidth;", driverScope).ToString(); + var initalHeight = Driver.ExecuteScript("return window.outerHeight;", driverScope).ToString(); + + Assert.That(int.Parse(initalWidth), Is.Not.EqualTo(768)); + Assert.That(int.Parse(initalHeight), Is.Not.EqualTo(400)); + + Driver.ResizeTo(new Size(768, 400), driverScope); + + Assert.That(Driver.ExecuteScript("return window.outerWidth;", driverScope).ToString(), Is.EqualTo("768")); + Assert.That(Driver.ExecuteScript("return window.outerHeight;", driverScope).ToString(), Is.EqualTo("400")); + } + } +} diff --git a/src/Coypu.Drivers.Tests/html/InteractionTestsPage.htm b/src/Coypu.Drivers.Tests/html/InteractionTestsPage.htm index 027d0c9c..7170b543 100644 --- a/src/Coypu.Drivers.Tests/html/InteractionTestsPage.htm +++ b/src/Coypu.Drivers.Tests/html/InteractionTestsPage.htm @@ -1,448 +1,451 @@ - - - - - Coypu interaction tests page - - - - -
- -
- Buttons - - - - - - - - - - - - - - - - - - - - - - - - - I'm an image with the role of button - - I'm a span with the role of button - I'm a span with the class of button - I'm a span with the class of btn - - - - - - - -
-
- Fields - - - - - - - i - - - - - - - - - - - - - - normalize space test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- select field 1: - -
- select field 2: - -
- -
- Ambiguous fields when finding by label text - - - - - - - - - - - - - - -
-
-
-

Links

- first link - - second link - - - - I'm a link with "both" types of quote in my text -
-
-

Inspecting Content

-
- Some text in a span - Some text with parts marked up variously. - - - - - - - - - -
Sometextinatablerow
-
    -
  • Some
  • -
  • text
  • -
  • in
  • -
  • a
  • -
  • list
  • -
- - Some - text - split - over - multiple - lines - in - source - - -
-

Some text displayed over

-

two paragraphs

- - Some text displayed over
- multiple lines -
- -

- This is what we are looking for - - -

- -
    -
  • one
  • -
  • two
  • -
  • Me! Pick me!
  • -
- - -
-
-
-

Dialogs

- - Trigger an alert - - - - Trigger a confirm - - - - I have a title - -
-
- -
- - - -
-
- Scope 1 - - - - - - - - - - - -
-
- -
-
- Scope 2 - - - - - - - - - - - - -
-
-
-

Section One h1

-

Section One h2

-

Section One h3

-
Section One h6
-
-
-

Section Two h1

-

Section Two h2

-

Section Two h3

-
Section Two h6
-
-
-

Div Section One h1

-

Div Section One h2

-

Div Section One h3

-
Div Section One h6
-
-
-

Div Section Two h1

-

Div Section Two h2

-

Div Section Two h3

-
Div Section Two h6
-
- - - - -
- - -
- - -
-

Hovering

- Hover on me -
- - - - - Open pop up window - - - Open pop up window 2 - - - - + + + + + Coypu interaction tests page + + + + +
+ +
+ Buttons + + + + + + + + + + + + + + + + + + + + + + + + + I'm an image with the role of button + + I'm a span with the role of button + I'm a span with the class of button + I'm a span with the class of btn + + + + + + + +
+
+ Fields + + + + + + + i + + + + + + + + + + + + + + normalize space test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ select field 1: + +
+ select field 2: + +
+ +
+ Ambiguous fields when finding by label text + + + + + + + + + + + + + + +
+
+
+

Links

+ first link + + second link + + + + I'm a link with "both" types of quote in my text +
+
+

Inspecting Content

+
+ Some text in a span + Some text with parts marked up variously. + + + + + + + + + +
Sometextinatablerow
+
    +
  • Some
  • +
  • text
  • +
  • in
  • +
  • a
  • +
  • list
  • +
+ + Some + text + split + over + multiple + lines + in + source + + +
+

Some text displayed over

+

two paragraphs

+ + Some text displayed over
+ multiple lines +
+ +

+ This is what we are looking for + + +

+ +
    +
  • one
  • +
  • two
  • +
  • Me! Pick me!
  • +
+ + +
+
+
+

Dialogs

+ + Trigger an alert + + + + Trigger a confirm + + + + I have a title + + + Trigger a prompt + +
+
+ +
+ + + +
+
+ Scope 1 + + + + + + + + + + + +
+
+ +
+
+ Scope 2 + + + + + + + + + + + + +
+
+
+

Section One h1

+

Section One h2

+

Section One h3

+
Section One h6
+
+
+

Section Two h1

+

Section Two h2

+

Section Two h3

+
Section Two h6
+
+
+

Div Section One h1

+

Div Section One h2

+

Div Section One h3

+
Div Section One h6
+
+
+

Div Section Two h1

+

Div Section Two h2

+

Div Section Two h3

+
Div Section Two h6
+
+ + + + +
+ + +
+ + +
+

Hovering

+ Hover on me +
+ + + + + Open pop up window + + + Open pop up window 2 + + + + diff --git a/src/Coypu.Drivers.Tests/html/iFrame1.htm b/src/Coypu.Drivers.Tests/html/iFrame1.htm index 1f938b14..30bef626 100644 --- a/src/Coypu.Drivers.Tests/html/iFrame1.htm +++ b/src/Coypu.Drivers.Tests/html/iFrame1.htm @@ -2,6 +2,7 @@ + iframe one title

I am iframe one

diff --git a/src/Coypu.Drivers.Tests/html/iFrame2.htm b/src/Coypu.Drivers.Tests/html/iFrame2.htm index 8df92269..9822ef8c 100644 --- a/src/Coypu.Drivers.Tests/html/iFrame2.htm +++ b/src/Coypu.Drivers.Tests/html/iFrame2.htm @@ -2,6 +2,7 @@ + iframe two title

I am iframe two

diff --git a/src/Coypu.Tests/TestBuilders/FindsFirstWitNoDisambiguationStrategy.cs b/src/Coypu.Tests/TestBuilders/FindsFirstWitNoDisambiguationStrategy.cs index 23136798..ba517016 100644 --- a/src/Coypu.Tests/TestBuilders/FindsFirstWitNoDisambiguationStrategy.cs +++ b/src/Coypu.Tests/TestBuilders/FindsFirstWitNoDisambiguationStrategy.cs @@ -1,25 +1,40 @@ -using System.Linq; -using Coypu.Finders; - -namespace Coypu.Tests.TestBuilders -{ - public class FirstOrDefaultNoDisambiguationStrategy : DisambiguationStrategy - { - public Element ResolveQuery(ElementFinder elementFinder) - { - return elementFinder.Find(elementFinder.Options).FirstOrDefault(); - } - } - - public class ThrowsWhenMissingButNoDisambiguationStrategy : DisambiguationStrategy - { - public Element ResolveQuery(ElementFinder elementFinder) - { - var all = elementFinder.Find(elementFinder.Options).ToArray(); - if (!all.Any()) - throw elementFinder.GetMissingException(); - - return all.First(); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using Coypu.Finders; + +namespace Coypu.Tests.TestBuilders +{ + public class FirstOrDefaultNoDisambiguationStrategy : DisambiguationStrategy + { + public Element ResolveQuery(ElementFinder elementFinder) + { + return elementFinder.Find(elementFinder.Options).FirstOrDefault(); + } + } + + public class ThrowsWhenMissingButNoDisambiguationStrategy : DisambiguationStrategy + { + public Element ResolveQuery(ElementFinder elementFinder) + { + var all = elementFinder.Find(elementFinder.Options); + var array = AsArray(all, elementFinder); + if (!array.Any()) + throw elementFinder.GetMissingException(); + + return all.First(); + } + + private static Element[] AsArray(IEnumerable all, ElementFinder elementFinder) + { + try { + return all.ToArray(); + } + catch(InvalidOperationException) { + // Elements have changed due to async page behaviour + throw elementFinder.GetMissingException(); + } + } + } +} diff --git a/src/Coypu.Tests/TestDoubles/FakeDriver.cs b/src/Coypu.Tests/TestDoubles/FakeDriver.cs index 25f88220..c8203c8f 100644 --- a/src/Coypu.Tests/TestDoubles/FakeDriver.cs +++ b/src/Coypu.Tests/TestDoubles/FakeDriver.cs @@ -1,375 +1,453 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.Linq; -using System.Text.RegularExpressions; -using Coypu.Drivers; -using OpenQA.Selenium; -using Cookie = System.Net.Cookie; - -namespace Coypu.Tests.TestDoubles -{ - public class FakeDriver : IDriver - { - public readonly IList CheckedElements = new List(); - public readonly IList ChosenElements = new List(); - public readonly IList ClickedElements = new List(); - public readonly IList FindXPathRequests = new List(); - public readonly IList GoBackCalls = new List(); - public readonly IList GoForwardCalls = new List(); - public readonly IList HoveredElements = new List(); - public readonly IList MaximiseWindowCalls = new List(); - public List ModalDialogsAccepted = new List(); - public List ModalDialogsCancelled = new List(); - public readonly IList RefreshCalls = new List(); - public readonly IList> ResizeToCalls = new List>(); - public readonly IList> SaveScreenshotCalls = new List>(); - public readonly IDictionary SentKeys = new Dictionary(); - public readonly IDictionary SetFields = new Dictionary(); - public readonly IList UncheckedElements = new List(); - public readonly IList> Visits = new List>(); - private readonly IList _stubbedAllCssResults = new List(); - private readonly IList _stubbedAllXPathResults = new List(); - private IList _stubbedCookies; - private readonly IList _stubbedExecuteScriptResults = new List(); - private readonly IList _stubbedFrames = new List(); - private readonly IList _stubbedHasDialogResults = new List(); - private readonly IList _stubbedLocations = new List(); - private readonly IList _stubbedTitles = new List(); - private readonly IList _stubbedWindows = new List(); - - public FakeDriver() { } - - public FakeDriver(Browser browser) - { - Browser = browser; - } - - public FakeDriver(IWebDriver driver) - { - Cookies = new Cookies(driver); - } - - public Browser Browser { get; } - - public void ClearBrowserCookies() - { - _stubbedCookies.Clear(); - } - - public void Click(Element element) - { - ClickedElements.Add(element); - } - - public void Hover(Element element) - { - HoveredElements.Add(element); - } - - public IEnumerable GetBrowserCookies() - { - return _stubbedCookies; - } - - public void Visit(string url, - Scope scope) - { - Visits.Add(new ScopedRequest {Request = url, Scope = scope}); - } - - public void Dispose() - { - Disposed = true; - } - - public bool Disposed { get; private set; } - - public Uri Location(Scope scope) - { - return Find(_stubbedLocations, null, scope); - } - - public string Title(Scope scope) - { - return Find(_stubbedTitles, null, scope); - } - - public Element Window { get; private set; } - - public void AcceptModalDialog(Scope scope) - { - ModalDialogsAccepted.Add(scope); - } - - public void CancelModalDialog(Scope scope) - { - ModalDialogsCancelled.Add(scope); - } - - public object ExecuteScript(string javascript, - Scope scope, - params object[] args) - { - return Find(_stubbedExecuteScriptResults, javascript, scope); - } - - public IEnumerable FindFrames(string locator, - Scope scope, - Options options) - { - return Find>(_stubbedFrames, locator, scope, options); - } - - public void SendKeys(Element element, - string keys) - { - SentKeys.Add(element, keys); - } - - public void MaximiseWindow(Scope scope) - { - MaximiseWindowCalls.Add(scope); - } - - public void Refresh(Scope scope) - { - RefreshCalls.Add(scope); - } - - public void ResizeTo(Size size, - Scope scope) - { - ResizeToCalls.Add(new ScopedRequest {Request = size, Scope = scope}); - } - - public void SaveScreenshot(string fileName, - Scope scope) - { - SaveScreenshotCalls.Add(new ScopedRequest - { - Request = fileName, - Scope = scope - }); - } - - public void GoBack(Scope scope) - { - GoBackCalls.Add(scope); - } - - public void GoForward(Scope scope) - { - GoForwardCalls.Add(scope); - } - - public void Set(Element element, - string value) - { - SetFields.Add(element, new SetFieldParams {Value = value}); - } - - public Cookies Cookies { get; set; } - - public object Native => "Native driver on fake driver"; - - public bool HasDialog(string withText, - Scope scope) - { - return Find(_stubbedHasDialogResults, withText, scope); - } - - public IEnumerable FindAllCss(string cssSelector, - Scope scope, - Options options, - Regex textPattern = null) - { - return Find>(_stubbedAllCssResults, cssSelector, scope, options, textPattern); - } - - public IEnumerable FindAllXPath(string xpath, - Scope scope, - Options options) - { - FindXPathRequests.Add(new FindXPathParams {XPath = xpath, Scope = scope, Options = options}); - return Find>(_stubbedAllXPathResults, xpath, scope, options); - } - - public void Check(Element field) - { - CheckedElements.Add(field); - } - - public void Uncheck(Element field) - { - UncheckedElements.Add(field); - } - - public void Choose(Element field) - { - ChosenElements.Add(field); - } - - public IEnumerable FindWindows(string locator, - Scope scope, - Options options) - { - return Find>(_stubbedWindows, locator, scope, options); - } - - public void SetBrowserCookies(Cookie cookie) { } - - public void StubDialog(string text, - bool result, - Scope scope) - { - _stubbedHasDialogResults.Add(new ScopedStubResult {Locator = text, Scope = scope, Result = result}); - } - - public void StubAllCss(string cssSelector, - IEnumerable result, - Scope scope, - Options options) - { - _stubbedAllCssResults.Add(new ScopedStubResult {Locator = cssSelector, Scope = scope, Result = result, Options = options}); - } - - public void StubAllXPath(string xpath, - IEnumerable result, - Scope scope, - Options options) - { - _stubbedAllXPathResults.Add(new ScopedStubResult {Locator = xpath, Scope = scope, Result = result, Options = options}); - } - - public void StubExecuteScript(string script, - string scriptReturnValue, - Scope scope) - { - _stubbedExecuteScriptResults.Add(new ScopedStubResult {Locator = script, Scope = scope, Result = scriptReturnValue}); - } - - public void StubFrames(string locator, - Element frame, - Scope scope, - Options options) - { - _stubbedFrames.Add(new ScopedStubResult {Locator = locator, Scope = scope, Result = frame, Options = options}); - } - - public void StubId(string id, - Element element, - Scope scope, - SessionConfiguration options) - { - StubAllXPath(new Html(options.Browser.UppercaseTagNames).Id(id, options), new[] {element}, scope, options); - } - - public void StubCookies(List cookies) - { - _stubbedCookies = cookies; - } - - public void StubLocation(Uri location, - Scope scope) - { - _stubbedLocations.Add(new ScopedStubResult {Result = location, Scope = scope}); - } - - public void StubTitle(string title, - Scope scope) - { - _stubbedTitles.Add(new ScopedStubResult {Result = title, Scope = scope}); - } - - public void StubWindow(string locator, - Element window, - Scope scope, - Options options) - { - _stubbedWindows.Add(new ScopedStubResult {Locator = locator, Scope = scope, Result = window, Options = options}); - } - - public void StubCurrentWindow(Element window) - { - Window = window; - } - - private T Find(IEnumerable stubbed, - string locator, - Scope scope, - Options options = null, - Regex textPattern = null) - { - var stubResult = stubbed - .FirstOrDefault( - r => - r.Locator == locator && (r.Scope == scope || scope.Now() == r.Scope.Now()) && r.TextPattern == textPattern && options == r.Options); - - if (stubResult == null) - throw new MissingHtmlException("No stubbed result found for: " + locator); - - return (T) stubResult.Result; - } - - private IEnumerable FindAll(IEnumerable stubbed, - object locator, - Scope scope, - Options options = null, - Regex textPattern = null) - { - var stubResult = stubbed.FirstOrDefault(r => r.Locator == locator.ToString() && r.Scope == scope && r.TextPattern == textPattern && options == r.Options); - if (stubResult == null) - return Enumerable.Empty(); - - return (IEnumerable) stubResult.Result; - } - - private static bool RegexEqual(Regex a, - Regex b) - { - return a.ToString() == b.ToString() && a.Options == b.Options; - } - - public class SaveScreenshotParams - { - public ImageFormat ImageFormat; - public string SaveAs; - } - - private class ScopedStubResult - { - public string Locator; - public Options Options; - public object Result; - public Scope Scope; - public readonly Regex TextPattern; - public ScopedStubResult() { } - - public ScopedStubResult(Regex textPattern) - { - TextPattern = textPattern; - } - } - - public class ScopedRequest - { - public T Request; - public Scope Scope; - } - } - - public class SetFieldParams - { - public string Value { get; set; } - } - - public class FindXPathParams - { - public Options Options { get; set; } - public Scope Scope { get; set; } - public Regex TextPattern { get; set; } - public string XPath { get; set; } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text.RegularExpressions; +using Coypu.Drivers; +using OpenQA.Selenium; +using Cookie = System.Net.Cookie; + +namespace Coypu.Tests.TestDoubles +{ + public class FakeDriver : IDriver + { + public readonly IList CheckedElements = new List(); + public readonly IList ChosenElements = new List(); + public readonly List SelectedOptions = new List(); + public readonly IList ClickedElements = new List(); + public readonly IList FindXPathRequests = new List(); + public readonly IList GoBackCalls = new List(); + public readonly IList GoForwardCalls = new List(); + public readonly IList HoveredElements = new List(); + public readonly IList MaximiseWindowCalls = new List(); + public List ModalDialogsAccepted = new List(); + public List ModalDialogsCancelled = new List(); + public readonly IList RefreshCalls = new List(); + public readonly IList> ResizeToCalls = new List>(); + public readonly IList> SaveScreenshotCalls = new List>(); + public readonly IDictionary SentKeys = new Dictionary(); + public readonly IDictionary SetFields = new Dictionary(); + public readonly IList UncheckedElements = new List(); + public readonly IList> Visits = new List>(); + private readonly IList _stubbedAllCssResults = new List(); + private readonly IList _stubbedAllXPathResults = new List(); + private IList _stubbedCookies; + private readonly IList _stubbedExecuteScriptResults = new List(); + private readonly IList _stubbedFrames = new List(); + private readonly IList _stubbedHasDialogResults = new List(); + private readonly IList _stubbedLocations = new List(); + private readonly IList _stubbedTitles = new List(); + private readonly IList _stubbedWindows = new List(); + + public FakeDriver() { } + + public FakeDriver(Browser browser, bool headless) + { + Browser = browser; + } + + public FakeDriver(IWebDriver driver, bool headless) + { + Cookies = new FakeCookies(); + } + + public Browser Browser { get; } + + public void ClearBrowserCookies() + { + _stubbedCookies.Clear(); + } + + public void Click(Element element) + { + ClickedElements.Add(element); + } + + public void Hover(Element element) + { + HoveredElements.Add(element); + } + + public IEnumerable GetBrowserCookies() + { + return _stubbedCookies; + } + + public void Visit(string url, + Scope scope) + { + Visits.Add(new ScopedRequest {Request = url, Scope = scope}); + } + + public void Dispose() + { + Disposed = true; + } + + public bool Disposed { get; private set; } + + public Uri Location(Scope scope) + { + return Find(_stubbedLocations, null, scope); + } + + public string Title(Scope scope) + { + return Find(_stubbedTitles, null, scope); + } + + public Element Window { get; private set; } + + public void AcceptModalDialog(Scope scope) + { + ModalDialogsAccepted.Add(scope); + } + + public void CancelModalDialog(Scope scope) + { + ModalDialogsCancelled.Add(scope); + } + + public object ExecuteScript(string javascript, + Scope scope, + params object[] args) + { + return Find(_stubbedExecuteScriptResults, javascript, scope); + } + + public IEnumerable FindFrames(string locator, + Scope scope, + Options options) + { + return Find>(_stubbedFrames, locator, scope, options); + } + + public void SendKeys(Element element, + string keys) + { + SentKeys.Add(element, keys); + } + + public void MaximiseWindow(Scope scope) + { + MaximiseWindowCalls.Add(scope); + } + + public void Refresh(Scope scope) + { + RefreshCalls.Add(scope); + } + + public void ResizeTo(Size size, + Scope scope) + { + ResizeToCalls.Add(new ScopedRequest {Request = size, Scope = scope}); + } + + public void SaveScreenshot(string fileName, + Scope scope) + { + SaveScreenshotCalls.Add(new ScopedRequest + { + Request = fileName, + Scope = scope + }); + } + + public void GoBack(Scope scope) + { + GoBackCalls.Add(scope); + } + + public void GoForward(Scope scope) + { + GoForwardCalls.Add(scope); + } + + public void Set(Element element, + string value) + { + SetFields.Add(element, new SetFieldParams {Value = value}); + } + + public Cookies Cookies { get; set; } + + public object Native => "Native driver on fake driver"; + + public bool HasDialog(string withText, + Scope scope) + { + return Find(_stubbedHasDialogResults, withText, scope); + } + + public IEnumerable FindAllCss(string cssSelector, + Scope scope, + Options options, + Regex textPattern = null) + { + return Find>(_stubbedAllCssResults, cssSelector, scope, options, textPattern); + } + + public IEnumerable FindAllXPath(string xpath, + Scope scope, + Options options) + { + FindXPathRequests.Add(new FindXPathParams {XPath = xpath, Scope = scope, Options = options}); + return Find>(_stubbedAllXPathResults, xpath, scope, options); + } + + public void Check(Element field) + { + CheckedElements.Add(field); + } + + public void Uncheck(Element field) + { + UncheckedElements.Add(field); + } + + public void Choose(Element field) + { + ChosenElements.Add(field); + } + + public void SelectOption(Element select, Element option, string optionToSelect) + { + SelectedOptions.Add(new SelectOptionParams {Select = select, Option = option, OptionToSelect = optionToSelect}); + } + + public IEnumerable FindWindows(string locator, + Scope scope, + Options options) + { + return Find>(_stubbedWindows, locator, scope, options); + } + + public void SetBrowserCookies(Cookie cookie) { } + + public void StubDialog(string text, + bool result, + Scope scope) + { + _stubbedHasDialogResults.Add(new ScopedStubResult {Locator = text, Scope = scope, Result = result}); + } + + public void StubAllCss(string cssSelector, + IEnumerable result, + Scope scope, + Options options) + { + _stubbedAllCssResults.Add(new ScopedStubResult {Locator = cssSelector, Scope = scope, Result = result, Options = options}); + } + + public void StubAllXPath(string xpath, + IEnumerable result, + Scope scope, + Options options) + { + _stubbedAllXPathResults.Add(new ScopedStubResult {Locator = xpath, Scope = scope, Result = result, Options = options}); + } + + public void StubExecuteScript(string script, + string scriptReturnValue, + Scope scope) + { + _stubbedExecuteScriptResults.Add(new ScopedStubResult {Locator = script, Scope = scope, Result = scriptReturnValue}); + } + + public void StubFrames(string locator, + Element frame, + Scope scope, + Options options) + { + _stubbedFrames.Add(new ScopedStubResult {Locator = locator, Scope = scope, Result = frame, Options = options}); + } + + public void StubId(string id, + Element element, + Scope scope, + SessionConfiguration options) + { + StubAllXPath(new Html(options.Browser.UppercaseTagNames).Id(id, options), new[] {element}, scope, options); + } + + public void StubCookies(List cookies) + { + _stubbedCookies = cookies; + } + + public void StubLocation(Uri location, + Scope scope) + { + _stubbedLocations.Add(new ScopedStubResult {Result = location, Scope = scope}); + } + + public void StubTitle(string title, + Scope scope) + { + _stubbedTitles.Add(new ScopedStubResult {Result = title, Scope = scope}); + } + + public void StubWindow(string locator, + Element window, + Scope scope, + Options options) + { + _stubbedWindows.Add(new ScopedStubResult {Locator = locator, Scope = scope, Result = window, Options = options}); + } + + public void StubCurrentWindow(Element window) + { + Window = window; + } + + private T Find(IEnumerable stubbed, + string locator, + Scope scope, + Options options = null, + Regex textPattern = null) + { + var stubResult = stubbed + .FirstOrDefault( + r => + r.Locator == locator && (r.Scope == scope || scope.Now() == r.Scope.Now()) && r.TextPattern == textPattern && options == r.Options); + + if (stubResult == null) + throw new MissingHtmlException("No stubbed result found for: " + locator); + + return (T) stubResult.Result; + } + + private IEnumerable FindAll(IEnumerable stubbed, + object locator, + Scope scope, + Options options = null, + Regex textPattern = null) + { + var stubResult = stubbed.FirstOrDefault(r => r.Locator == locator.ToString() && r.Scope == scope && r.TextPattern == textPattern && options == r.Options); + if (stubResult == null) + return Enumerable.Empty(); + + return (IEnumerable) stubResult.Result; + } + + private static bool RegexEqual(Regex a, + Regex b) + { + return a.ToString() == b.ToString() && a.Options == b.Options; + } + + public void AcceptAlert(string text, DriverScope root, Action trigger) + { + throw new NotImplementedException(); + } + + public void AcceptConfirm(string text, DriverScope root, Action trigger) + { + throw new NotImplementedException(); + } + + public void CancelConfirm(string text, DriverScope root, Action trigger) + { + throw new NotImplementedException(); + } + + public void AcceptPrompt(string text, string promptValue, DriverScope root, Action trigger) + { + throw new NotImplementedException(); + } + + public void CancelPrompt(string text, DriverScope root, Action trigger) + { + throw new NotImplementedException(); + } + + public class SaveScreenshotParams + { + public ImageFormat ImageFormat; + public string SaveAs; + } + + private class ScopedStubResult + { + public string Locator; + public Options Options; + public object Result; + public Scope Scope; + public readonly Regex TextPattern; + public ScopedStubResult() { } + + public ScopedStubResult(Regex textPattern) + { + TextPattern = textPattern; + } + } + + public class ScopedRequest + { + public T Request; + public Scope Scope; + } + + public class SelectOptionParams + { + public Element Select; + public Element Option; + public string OptionToSelect; + } + } + + public class SetFieldParams + { + public string Value { get; set; } + } + + public class FindXPathParams + { + public Options Options { get; set; } + public Scope Scope { get; set; } + public Regex TextPattern { get; set; } + public string XPath { get; set; } + } + + // Implementation of Cookies interface with in memory stores for cookies that behaves like a real browser might + public class FakeCookies : Cookies { + private readonly List _cookies = new List(); + + public void AddCookie(Cookie cookie, Options options = null) + { + _cookies.Add(cookie); + } + + public void DeleteAll() + { + _cookies.Clear(); + } + + public void DeleteCookie(Cookie cookie) + { + _cookies.Remove(cookie); + } + + public void DeleteCookieNamed(string cookieName) + { + _cookies.RemoveAll(c => c.Name == cookieName); + } + + public IEnumerable GetAll() + { + return _cookies; + } + + public Cookie GetCookieNamed(string cookieName) + { + return _cookies.FirstOrDefault(c => c.Name == cookieName); + } + + public void WaitUntilCookieExists(Cookie cookie, Options options) + { + System.Threading.Thread.Sleep(1); + } + } +} diff --git a/src/Coypu.Tests/TestDoubles/StubDriver.cs b/src/Coypu.Tests/TestDoubles/StubDriver.cs index e35997cf..a805cfca 100644 --- a/src/Coypu.Tests/TestDoubles/StubDriver.cs +++ b/src/Coypu.Tests/TestDoubles/StubDriver.cs @@ -1,153 +1,204 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text.RegularExpressions; -using Coypu.Drivers; -using OpenQA.Selenium; -using Cookie = System.Net.Cookie; - -namespace Coypu.Tests.TestDoubles -{ - public class StubDriver : IDriver - { - public StubDriver() { } - - public StubDriver(Browser browser) { } - - public StubDriver(IWebDriver driver) - { - Cookies = new Cookies(driver); - } - - public void Dispose() { } - - public void ClearBrowserCookies() { } - - public void Click(Element element) { } - - public void Visit(string url, - Scope scope) { } - - public void Set(Element element, - string value) { } - - public Cookies Cookies { get; set; } - - public object Native => "Native driver on stub driver"; - - public bool HasDialog(string withText, - Scope scope) - { - return false; - } - - public IEnumerable FindAllCss(string cssSelector, - Scope scope, - Options options, - Regex textPattern = null) - { - return Enumerable.Empty(); - } - - public IEnumerable FindAllXPath(string xpath, - Scope scope, - Options options) - { - return Enumerable.Empty(); - } - - public void Check(Element field) { } - - public void Uncheck(Element field) { } - - public void Choose(Element field) { } - - public bool Disposed => false; - - Uri IDriver.Location(Scope scope) - { - return null; - } - - public string Title(Scope scope) - { - return null; - } - - public Element Window => null; - - public void AcceptModalDialog(Scope scope) { } - - public void CancelModalDialog(Scope scope) { } - - public object ExecuteScript(string javascript, - Scope scope, - params object[] args) - { - return null; - } - - public void Hover(Element element) { } - - public IEnumerable GetBrowserCookies() - { - return new List(); - } - - public IEnumerable FindWindows(string locator, - Scope scope, - Options options) - { - return Enumerable.Empty(); - } - - public IEnumerable FindFrames(string locator, - Scope scope, - Options options) - { - return Enumerable.Empty(); - } - - public void SendKeys(Element element, - string keys) { } - - public void MaximiseWindow(Scope scope) { } - - public void Refresh(Scope scope) { } - - public void ResizeTo(Size size, - Scope Scope) { } - - public void SaveScreenshot(string fileName, - Scope scope) { } - - public void GoBack(Scope scope) { } - - public void GoForward(Scope scope) { } - - public bool HasContent(string text, - Scope scope) - { - return false; - } - - public bool HasContentMatch(Regex pattern, - Scope scope) - { - return false; - } - - public void SetScope(Element findScope) { } - - public void ClearScope() { } - - public Element FindIFrame(string locator, - Scope scope) - { - return null; - } - - public void SetBrowserCookies(Cookie cookie) { } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text.RegularExpressions; +using Coypu.Drivers; +using OpenQA.Selenium; +using Cookie = System.Net.Cookie; + +namespace Coypu.Tests.TestDoubles +{ + public class StubDriver : IDriver + { + public StubDriver() { + Cookies = new StubCookies(); + } + + public StubDriver(Browser browser, bool headless) : this() { } + + public void Dispose() { } + + public void ClearBrowserCookies() { } + + public void Click(Element element) { } + + public void Visit(string url, + Scope scope) { } + + public void Set(Element element, + string value) { } + + public Cookies Cookies { get; set; } + + public object Native => "Native driver on stub driver"; + + public bool HasDialog(string withText, + Scope scope) + { + return false; + } + + public IEnumerable FindAllCss(string cssSelector, + Scope scope, + Options options, + Regex textPattern = null) + { + return Enumerable.Empty(); + } + + public IEnumerable FindAllXPath(string xpath, + Scope scope, + Options options) + { + return Enumerable.Empty(); + } + + public void Check(Element field) { } + + public void Uncheck(Element field) { } + + public void Choose(Element field) { } + + public void SelectOption(Element select, Element option, string optionToSelect) { } + + public bool Disposed => false; + + Uri IDriver.Location(Scope scope) + { + return null; + } + + public string Title(Scope scope) + { + return null; + } + + public Element Window => null; + + public void AcceptModalDialog(Scope scope) { } + + public void CancelModalDialog(Scope scope) { } + + public object ExecuteScript(string javascript, + Scope scope, + params object[] args) + { + return null; + } + + public void Hover(Element element) { } + + public IEnumerable GetBrowserCookies() + { + return new List(); + } + + public IEnumerable FindWindows(string locator, + Scope scope, + Options options) + { + return Enumerable.Empty(); + } + + public IEnumerable FindFrames(string locator, + Scope scope, + Options options) + { + return Enumerable.Empty(); + } + + public void SendKeys(Element element, + string keys) { } + + public void MaximiseWindow(Scope scope) { } + + public void Refresh(Scope scope) { } + + public void ResizeTo(Size size, + Scope Scope) { } + + public void SaveScreenshot(string fileName, + Scope scope) { } + + public void GoBack(Scope scope) { } + + public void GoForward(Scope scope) { } + + public bool HasContent(string text, + Scope scope) + { + return false; + } + + public bool HasContentMatch(Regex pattern, + Scope scope) + { + return false; + } + + public void SetScope(Element findScope) { } + + public void ClearScope() { } + + public Element FindIFrame(string locator, + Scope scope) + { + return null; + } + + public void SetBrowserCookies(Cookie cookie) { } + + public void AcceptAlert(string text, DriverScope root, Action trigger) + { + trigger.Invoke(); + } + + public void AcceptConfirm(string text, DriverScope root, Action trigger) + { + trigger.Invoke(); + } + + public void CancelConfirm(string text, DriverScope root, Action trigger) + { + trigger.Invoke(); + } + + public void AcceptPrompt(string text, string promptValue, DriverScope root, Action trigger) + { + trigger.Invoke(); + } + + public void CancelPrompt(string text, DriverScope root, Action trigger) + { + trigger.Invoke(); + } + } + + // Implementation of Cookies interface that has no behaviour + public class StubCookies : Cookies + { + public void AddCookie(Cookie cookie, + Options options = null) { } + + public void DeleteAll() { } + + public void DeleteCookie(Cookie cookie) { } + + public void DeleteCookieNamed(string cookieName) { } + + public IEnumerable GetAll() + { + return new List(); + } + + public Cookie GetCookieNamed(string cookieName) + { + return null; + } + + public void WaitUntilCookieExists(Cookie cookie, + Options options) { + } + } +} diff --git a/src/Coypu.Tests/When_interacting_with_the_browser/BrowserInteractionTests.cs b/src/Coypu.Tests/When_interacting_with_the_browser/BrowserInteractionTests.cs index c113938e..cf107e1f 100644 --- a/src/Coypu.Tests/When_interacting_with_the_browser/BrowserInteractionTests.cs +++ b/src/Coypu.Tests/When_interacting_with_the_browser/BrowserInteractionTests.cs @@ -1,150 +1,150 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Coypu.Queries; -using Coypu.Timing; -using Coypu.Tests.TestBuilders; -using Coypu.Tests.TestDoubles; -using NUnit.Framework; - -namespace Coypu.Tests.When_interacting_with_the_browser -{ - public abstract class BrowserInteractionTests - { - protected FakeDriver driver; - protected FakeWaiter fakeWaiter; - protected BrowserSession browserSession; - protected SpyTimingStrategy SpyTimingStrategy; - protected StubUrlBuilder stubUrlBuilder; - protected SessionConfiguration sessionConfiguration; - protected ElementScope elementScope; - protected BrowserWindow popupScope; - - [SetUp] - public void SetUp() - { - driver = new FakeDriver(); - SpyTimingStrategy = new SpyTimingStrategy(); - fakeWaiter = new FakeWaiter(); - stubUrlBuilder = new StubUrlBuilder(); - sessionConfiguration = new SessionConfiguration(); - browserSession = TestSessionBuilder.Build(sessionConfiguration, driver, SpyTimingStrategy, fakeWaiter, new SpyRestrictedResourceDownloader(), - stubUrlBuilder); - - elementScope = browserSession.FindXPath("."); - popupScope = browserSession.FindWindow("popup"); - } - - protected object RunQueryAndCheckTiming() - { - return RunQueryAndCheckTiming(); - } - - protected object RunQueryAndCheckTiming(TimeSpan timeout) - { - return RunQueryAndCheckTiming(timeout); - } - - protected T RunQueryAndCheckTiming() - { - return RunQueryAndCheckTiming(sessionConfiguration.Timeout); - } - - protected T RunQueryAndCheckTiming(TimeSpan timeout) - { - var query = SpyTimingStrategy.QueriesRan().Single(); - return RunQueryAndCheckTiming(query, timeout); - } - - protected T RunQueryAndCheckTiming(TimeSpan timeout, int index) - { - var query = SpyTimingStrategy.QueriesRan().ElementAt(index); - return RunQueryAndCheckTiming(query, timeout); - } - - protected object RunQueryAndCheckTiming(int index) - { - var query = SpyTimingStrategy.QueriesRan().ElementAt(index); - return RunQueryAndCheckTiming(query); - } - - protected T RunQueryAndCheckTiming(Query query) - { - return RunQueryAndCheckTiming(query, sessionConfiguration.Timeout); - } - - protected T RunQueryAndCheckTiming(Query query, TimeSpan timeout) - { - SpyTimingStrategy.ExecuteImmediately = true; - var queryResult = query.Run(); - - Assert.That(query.Options.Timeout, Is.EqualTo(timeout)); - Assert.That(query.Options.RetryInterval, Is.EqualTo(sessionConfiguration.RetryInterval)); - - return queryResult; - } - - protected void VerifyFoundRobustly(Func scope, int driverCallIndex, string locator, StubElement expectedDeferredResult, StubElement expectedImmediateResult, Options options) - { - var sub = scope; - var scopedResult = sub(locator, options).Now(); - - Assert.That(scopedResult, Is.Not.SameAs(expectedDeferredResult), "Result was not found robustly"); - Assert.That(scopedResult, Is.SameAs(expectedImmediateResult)); - - var elementScopeResult = RunQueryAndCheckTiming(options.Timeout, driverCallIndex); - - Assert.That(elementScopeResult, Is.SameAs(expectedDeferredResult)); - } - - } - - public class StubDriverFactory : DriverFactory - { - private readonly IDriver driver; - - public StubDriverFactory(IDriver driver) - { - this.driver = driver; - } - - public IDriver NewWebDriver(Type driverType, Drivers.Browser browser) - { - return driver; - } - } - - public class StubUrlBuilder : UrlBuilder - { - private readonly Dictionary urls = new Dictionary(); - - public string GetFullyQualifiedUrl(string virtualPath, SessionConfiguration sessionConfiguration) - { - return urls[virtualPath]; - } - - public void SetStubUrl(string virtualPath, string url) - { - urls[virtualPath] = url; - } - } - - public class FakeWaiter : Waiter - { - private Action doOnWait = ms => { }; - - #region Waiter Members - - public void Wait(TimeSpan duration) - { - doOnWait(duration); - } - - #endregion - - public void DoOnWait(Action action) - { - doOnWait = action; - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Coypu.Queries; +using Coypu.Timing; +using Coypu.Tests.TestBuilders; +using Coypu.Tests.TestDoubles; +using NUnit.Framework; + +namespace Coypu.Tests.When_interacting_with_the_browser +{ + public abstract class BrowserInteractionTests + { + protected FakeDriver driver; + protected FakeWaiter fakeWaiter; + protected BrowserSession browserSession; + protected SpyTimingStrategy SpyTimingStrategy; + protected StubUrlBuilder stubUrlBuilder; + protected SessionConfiguration sessionConfiguration; + protected ElementScope elementScope; + protected BrowserWindow popupScope; + + [SetUp] + public void SetUp() + { + driver = new FakeDriver(); + SpyTimingStrategy = new SpyTimingStrategy(); + fakeWaiter = new FakeWaiter(); + stubUrlBuilder = new StubUrlBuilder(); + sessionConfiguration = new SessionConfiguration(); + browserSession = TestSessionBuilder.Build(sessionConfiguration, driver, SpyTimingStrategy, fakeWaiter, new SpyRestrictedResourceDownloader(), + stubUrlBuilder); + + elementScope = browserSession.FindXPath("."); + popupScope = browserSession.FindWindow("popup"); + } + + protected object RunQueryAndCheckTiming() + { + return RunQueryAndCheckTiming(); + } + + protected object RunQueryAndCheckTiming(TimeSpan timeout) + { + return RunQueryAndCheckTiming(timeout); + } + + protected T RunQueryAndCheckTiming() + { + return RunQueryAndCheckTiming(sessionConfiguration.Timeout); + } + + protected T RunQueryAndCheckTiming(TimeSpan timeout) + { + var query = SpyTimingStrategy.QueriesRan().Single(); + return RunQueryAndCheckTiming(query, timeout); + } + + protected T RunQueryAndCheckTiming(TimeSpan timeout, int index) + { + var query = SpyTimingStrategy.QueriesRan().ElementAt(index); + return RunQueryAndCheckTiming(query, timeout); + } + + protected object RunQueryAndCheckTiming(int index) + { + var query = SpyTimingStrategy.QueriesRan().ElementAt(index); + return RunQueryAndCheckTiming(query); + } + + protected T RunQueryAndCheckTiming(Query query) + { + return RunQueryAndCheckTiming(query, sessionConfiguration.Timeout); + } + + protected T RunQueryAndCheckTiming(Query query, TimeSpan timeout) + { + SpyTimingStrategy.ExecuteImmediately = true; + var queryResult = query.Run(); + + Assert.That(query.Options.Timeout, Is.EqualTo(timeout)); + Assert.That(query.Options.RetryInterval, Is.EqualTo(sessionConfiguration.RetryInterval)); + + return queryResult; + } + + protected void VerifyFoundRobustly(Func scope, int driverCallIndex, string locator, StubElement expectedDeferredResult, StubElement expectedImmediateResult, Options options) + { + var sub = scope; + var scopedResult = sub(locator, options).Now(); + + Assert.That(scopedResult, Is.Not.SameAs(expectedDeferredResult), "Result was not found robustly"); + Assert.That(scopedResult, Is.SameAs(expectedImmediateResult)); + + var elementScopeResult = RunQueryAndCheckTiming(options.Timeout, driverCallIndex); + + Assert.That(elementScopeResult, Is.SameAs(expectedDeferredResult)); + } + + } + + public class StubDriverFactory : DriverFactory + { + private readonly IDriver driver; + + public StubDriverFactory(IDriver driver) + { + this.driver = driver; + } + + public IDriver NewWebDriver(Type driverType, Drivers.Browser browser, bool Headless) + { + return driver; + } + } + + public class StubUrlBuilder : UrlBuilder + { + private readonly Dictionary urls = new Dictionary(); + + public string GetFullyQualifiedUrl(string virtualPath, SessionConfiguration sessionConfiguration) + { + return urls[virtualPath]; + } + + public void SetStubUrl(string virtualPath, string url) + { + urls[virtualPath] = url; + } + } + + public class FakeWaiter : Waiter + { + private Action doOnWait = ms => { }; + + #region Waiter Members + + public void Wait(TimeSpan duration) + { + doOnWait(duration); + } + + #endregion + + public void DoOnWait(Action action) + { + doOnWait = action; + } + } +} diff --git a/src/Coypu.Tests/When_interacting_with_the_browser/When_completing_forms.cs b/src/Coypu.Tests/When_interacting_with_the_browser/When_completing_forms.cs index 0d9c3a30..2ef10536 100644 --- a/src/Coypu.Tests/When_interacting_with_the_browser/When_completing_forms.cs +++ b/src/Coypu.Tests/When_interacting_with_the_browser/When_completing_forms.cs @@ -1,156 +1,160 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Coypu.Drivers; -using Coypu.Tests.TestDoubles; -using NUnit.Framework; - -namespace Coypu.Tests.When_interacting_with_the_browser -{ - [TestFixture] - public class When_completing_forms : BrowserInteractionTests - { - private StubElement StubField(string locator, Options options = null) - { - var stubField = new StubElement{Id = Guid.NewGuid().ToString()}; - var fieldXpath = new Html(sessionConfiguration.Browser.UppercaseTagNames).Field(locator, options ?? sessionConfiguration); - driver.StubAllXPath(fieldXpath, new []{stubField}, browserSession, options ?? sessionConfiguration); - - return stubField; - } - - private StubElement StubOption(string fieldLocator, string optionLocator, Options options = null) - { - var stubSelect = new StubElement { Id = Guid.NewGuid().ToString() }; - var stubOption = new StubElement { Id = Guid.NewGuid().ToString() }; - var selectXpath = new Html(sessionConfiguration.Browser.UppercaseTagNames).Select(fieldLocator, options ?? sessionConfiguration); - var optionXpath = new Html(sessionConfiguration.Browser.UppercaseTagNames).Option(optionLocator, options ?? sessionConfiguration); - driver.StubAllXPath(selectXpath, new[] { stubSelect }, browserSession, options ?? sessionConfiguration); - driver.StubAllXPath(optionXpath, new[] { stubOption }, new SnapshotElementScope(stubSelect, browserSession, options), options ?? sessionConfiguration); - - return stubOption; - } - - [Test] - public void When_filling_in_a_text_field_It_finds_field_and_sets_value_robustly() - { - var element = StubField("Some field locator"); - - browserSession.FillIn("Some field locator").With("some value for the field"); - - Assert.That(driver.SetFields.Keys, Has.No.Member(element)); - - RunQueryAndCheckTiming(); - - var setField = driver.SetFields.Keys.Cast().Single().Now(); - Assert.That(setField, Is.SameAs(element)); - Assert.That(driver.SetFields.Values.Single().Value, Is.EqualTo("some value for the field")); - } - - [Test] - public void When_filling_in_a_field_It_clicks_to_ensure_focus_event() - { - var element = StubField("Some field locator"); - - browserSession.FillIn("Some field locator").With("some value for the field"); - - Assert.That(driver.ClickedElements, Is.Empty); - - RunQueryAndCheckTiming(); - - AssertSingleElementEquals(element, driver.ClickedElements); - } - - [Test] - public void When_filling_in_file_field_It_doesnt_click() { - var element = StubField("Some field locator"); - element.StubAttribute("type", "file"); - - browserSession.FillIn("Some field locator").With("some value for the field"); - - RunQueryAndCheckTiming(); - - Assert.That(driver.ClickedElements, Is.Empty); - } - - [Test] - public void When_selecting_an_option_It_finds_field_and_clicks_the_option_synchronised() - { - var stubbedOption = StubOption("Some select field locator", "Some option to select"); - - browserSession.Select("Some option to select").From("Some select field locator"); - - Assert.That(driver.ClickedElements, Has.No.Member(stubbedOption)); - - RunQueryAndCheckTiming(); - - var selectedOption = driver.ClickedElements.Single(); - Assert.That(selectedOption, Is.SameAs(stubbedOption)); - } - - [Test] - public void When_checking_a_checkbox_It_find_fields_and_checks_robustly() - { - var element = StubField("Some checkbox locator"); - - browserSession.Check("Some checkbox locator"); - - Assert.That(driver.CheckedElements, Is.Empty); - - RunQueryAndCheckTiming(); - - AssertSingleElementEquals(element, driver.CheckedElements); - } - - private void AssertSingleElementEquals(StubElement element, IEnumerable elements) - { - Assert.That(elements.Cast().Single().Now(), Is.EqualTo(element)); - } - - [Test] - public void When_unchecking_a_checkbox_It_finds_field_and_unchecks_robustly() - { - var element = StubField("Some checkbox locator"); - - browserSession.Uncheck("Some checkbox locator"); - - Assert.That(driver.UncheckedElements, Has.No.Member(element)); - - RunQueryAndCheckTiming(); - - AssertSingleElementEquals(element, driver.UncheckedElements); - } - - [Test] - public void When_choosing_a_radio_button_It_finds_field_and_chooses_robustly() - { - var element = StubField("Some radio locator"); - - browserSession.Choose("Some radio locator"); - - Assert.That(driver.ChosenElements, Has.No.Member(element)); - - RunQueryAndCheckTiming(); - - AssertSingleElementEquals(element, driver.ChosenElements); - } - - [Test] - public void It_sends_keys_to_element_via_underlying_driver() - { - var element = new StubElement(); - driver.StubId("something", element, browserSession, sessionConfiguration); - SpyTimingStrategy.AlwaysReturnFromSynchronise(element); - - browserSession.FindId("something") - .SendKeys("some keys to press"); - - Assert.That(driver.SentKeys.Count, Is.EqualTo(0)); - - RunQueryAndCheckTiming(); - - Assert.That(driver.SentKeys.Count, Is.EqualTo(1)); - Assert.That(driver.SentKeys[element], Is.EqualTo("some keys to press")); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using Coypu.Drivers; +using Coypu.Tests.TestDoubles; +using NUnit.Framework; +using OpenQA.Selenium.DevTools.V85.Network; + +namespace Coypu.Tests.When_interacting_with_the_browser +{ + [TestFixture] + public class When_completing_forms : BrowserInteractionTests + { + private StubElement StubField(string locator, Options options = null) + { + var stubField = new StubElement{Id = Guid.NewGuid().ToString()}; + var fieldXpath = new Html(sessionConfiguration.Browser.UppercaseTagNames).Field(locator, options ?? sessionConfiguration); + driver.StubAllXPath(fieldXpath, new []{stubField}, browserSession, options ?? sessionConfiguration); + + return stubField; + } + + private FakeDriver.SelectOptionParams StubOption(string fieldLocator, string optionLocator, Options options = null) + { + var mergedOptions = Options.Merge(options, sessionConfiguration); + var stubSelect = new StubElement { Id = Guid.NewGuid().ToString() }; + var stubOption = new StubElement { Id = Guid.NewGuid().ToString() }; + var selectXpath = new Html(sessionConfiguration.Browser.UppercaseTagNames).Select(fieldLocator, mergedOptions); + var optionXpath = new Html(sessionConfiguration.Browser.UppercaseTagNames).Option(optionLocator, Options.Merge(mergedOptions, Options.Invisible)); + driver.StubAllXPath(selectXpath, new[] { stubSelect }, browserSession, mergedOptions); + driver.StubAllXPath(optionXpath, new[] { stubOption }, new SnapshotElementScope(stubSelect, browserSession, options), Options.Merge(mergedOptions, Options.Invisible)); + + return new FakeDriver.SelectOptionParams{Select = stubSelect, Option = stubOption, OptionToSelect = optionLocator}; + } + + [Test] + public void When_filling_in_a_text_field_It_finds_field_and_sets_value_robustly() + { + var element = StubField("Some field locator"); + + browserSession.FillIn("Some field locator").With("some value for the field"); + + Assert.That(driver.SetFields.Keys, Has.No.Member(element)); + + RunQueryAndCheckTiming(); + + var setField = driver.SetFields.Keys.Cast().Single().Now(); + Assert.That(setField, Is.SameAs(element)); + Assert.That(driver.SetFields.Values.Single().Value, Is.EqualTo("some value for the field")); + } + + [Test] + public void When_filling_in_a_field_It_clicks_to_ensure_focus_event() + { + var element = StubField("Some field locator"); + + browserSession.FillIn("Some field locator").With("some value for the field"); + + Assert.That(driver.ClickedElements, Is.Empty); + + RunQueryAndCheckTiming(); + + AssertSingleElementEquals(element, driver.ClickedElements); + } + + [Test] + public void When_filling_in_file_field_It_doesnt_click() { + var element = StubField("Some field locator"); + element.StubAttribute("type", "file"); + + browserSession.FillIn("Some field locator").With("some value for the field"); + + RunQueryAndCheckTiming(); + + Assert.That(driver.ClickedElements, Is.Empty); + } + + [Test] + public void When_selecting_an_option_It_finds_invisible_field_and_clicks_the_option_synchronised() + { + var stubbedOption = StubOption("Some select field locator", "Some option to select"); + + browserSession.Select("Some option to select").From("Some select field locator"); + + Assert.That(driver.SelectedOptions, Is.Empty); + + RunQueryAndCheckTiming(); + + var selectedOption = driver.SelectedOptions.Single(); + Assert.That(selectedOption.Option, Is.SameAs(stubbedOption.Option)); + Assert.That(((SnapshotElementScope)selectedOption.Select).FindElement(), Is.SameAs(stubbedOption.Select)); + Assert.That(selectedOption.OptionToSelect, Is.SameAs(stubbedOption.OptionToSelect)); + } + + [Test] + public void When_checking_a_checkbox_It_find_fields_and_checks_robustly() + { + var element = StubField("Some checkbox locator"); + + browserSession.Check("Some checkbox locator"); + + Assert.That(driver.CheckedElements, Is.Empty); + + RunQueryAndCheckTiming(); + + AssertSingleElementEquals(element, driver.CheckedElements); + } + + private void AssertSingleElementEquals(StubElement element, IEnumerable elements) + { + Assert.That(elements.Cast().Single().Now(), Is.EqualTo(element)); + } + + [Test] + public void When_unchecking_a_checkbox_It_finds_field_and_unchecks_robustly() + { + var element = StubField("Some checkbox locator"); + + browserSession.Uncheck("Some checkbox locator"); + + Assert.That(driver.UncheckedElements, Has.No.Member(element)); + + RunQueryAndCheckTiming(); + + AssertSingleElementEquals(element, driver.UncheckedElements); + } + + [Test] + public void When_choosing_a_radio_button_It_finds_field_and_chooses_robustly() + { + var element = StubField("Some radio locator"); + + browserSession.Choose("Some radio locator"); + + Assert.That(driver.ChosenElements, Has.No.Member(element)); + + RunQueryAndCheckTiming(); + + AssertSingleElementEquals(element, driver.ChosenElements); + } + + [Test] + public void It_sends_keys_to_element_via_underlying_driver() + { + var element = new StubElement(); + driver.StubId("something", element, browserSession, sessionConfiguration); + SpyTimingStrategy.AlwaysReturnFromSynchronise(element); + + browserSession.FindId("something") + .SendKeys("some keys to press"); + + Assert.That(driver.SentKeys.Count, Is.EqualTo(0)); + + RunQueryAndCheckTiming(); + + Assert.That(driver.SentKeys.Count, Is.EqualTo(1)); + Assert.That(driver.SentKeys[element], Is.EqualTo("some keys to press")); + } + } +} diff --git a/src/Coypu.Tests/When_interacting_with_the_browser/When_interacting_with_modal_dialogs.cs b/src/Coypu.Tests/When_interacting_with_the_browser/When_interacting_with_modal_dialogs.cs index cf56643b..58e5e091 100644 --- a/src/Coypu.Tests/When_interacting_with_the_browser/When_interacting_with_modal_dialogs.cs +++ b/src/Coypu.Tests/When_interacting_with_the_browser/When_interacting_with_modal_dialogs.cs @@ -1,30 +1,31 @@ -using System.Linq; -using NUnit.Framework; - -namespace Coypu.Tests.When_interacting_with_the_browser -{ - [TestFixture] - public class When_interacting_with_modal_dialogs : BrowserInteractionTests - { - [Test] - public void AcceptDialog_should_make_robust_call_to_underlying_driver() - { - browserSession.AcceptModalDialog(); - - Assert.That(driver.ModalDialogsAccepted.Any(), Is.False); - RunQueryAndCheckTiming(); - Assert.That(driver.ModalDialogsAccepted.Single(), Is.SameAs(browserSession)); - } - - [Test] - public void CancelDialog_should_make_robust_call_to_underlying_driver() - { - browserSession.CancelModalDialog(); - - Assert.That(driver.ModalDialogsCancelled.Any(), Is.False); - RunQueryAndCheckTiming(); - Assert.That(driver.ModalDialogsCancelled.Single(), Is.SameAs(browserSession)); - } - - } -} \ No newline at end of file +using System; +using System.Linq; +using NUnit.Framework; + +namespace Coypu.Tests.When_interacting_with_the_browser +{ + [TestFixture] + public class When_interacting_with_modal_dialogs : BrowserInteractionTests + { + [Test] + public void AcceptDialog_should_make_robust_call_to_underlying_driver() + { + browserSession.AcceptModalDialog(); + + Assert.That(driver.ModalDialogsAccepted.Any(), Is.False); + RunQueryAndCheckTiming(); + Assert.That(driver.ModalDialogsAccepted.Single(), Is.SameAs(browserSession)); + } + + [Test] + public void CancelDialog_should_make_robust_call_to_underlying_driver() + { + browserSession.CancelModalDialog(); + + Assert.That(driver.ModalDialogsCancelled.Any(), Is.False); + RunQueryAndCheckTiming(); + Assert.That(driver.ModalDialogsCancelled.Single(), Is.SameAs(browserSession)); + } + + } +} diff --git a/src/Coypu/Actions/Select.cs b/src/Coypu/Actions/Select.cs index 15447772..32b0ccb3 100644 --- a/src/Coypu/Actions/Select.cs +++ b/src/Coypu/Actions/Select.cs @@ -1,69 +1,69 @@ -using Coypu.Finders; - -namespace Coypu.Actions -{ - internal class Select : DriverAction - { - private readonly DisambiguationStrategy _disambiguationStrategy; - private Options _fromOptions; - private readonly string _locator; - private readonly Options _options; - private readonly string _optionToSelect; - private ElementScope _selectElement; - - internal Select(IDriver driver, - DriverScope scope, - string locator, - string optionToSelect, - DisambiguationStrategy disambiguationStrategy, - Options options) : base(driver, scope, options) - { - _locator = locator; - _optionToSelect = optionToSelect; - _options = options; - _disambiguationStrategy = disambiguationStrategy; - } - - internal Select(IDriver driver, - DriverScope scope, - string locator, - string optionToSelect, - DisambiguationStrategy disambiguationStrategy, - Options options, - Options fromOptions) : this(driver, scope, locator, optionToSelect, disambiguationStrategy, options) - { - _fromOptions = fromOptions; - } - - internal Select(IDriver driver, - ElementScope selectElement, - string optionToSelect, - DisambiguationStrategy disambiguationStrategy, - Options options) : base(driver, selectElement, options) - { - _selectElement = selectElement; - _optionToSelect = optionToSelect; - _options = options; - _disambiguationStrategy = disambiguationStrategy; - } - - public override void Act() - { - _selectElement = _selectElement ?? FindSelectElement(); - SelectOption(_selectElement); - } - - private SnapshotElementScope FindSelectElement() - { - if (_fromOptions == null) _fromOptions = _options; - var selectElementFound = _disambiguationStrategy.ResolveQuery(new SelectFinder(Driver, _locator, Scope, _fromOptions)); - return new SnapshotElementScope(selectElementFound, Scope, _fromOptions); - } - - private void SelectOption(ElementScope selectElementScope) - { - var option = _disambiguationStrategy.ResolveQuery(new OptionFinder(Driver, _optionToSelect, selectElementScope, _options)); - Driver.Click(option); - } - } -} \ No newline at end of file +using Coypu.Finders; + +namespace Coypu.Actions +{ + internal class Select : DriverAction + { + private readonly DisambiguationStrategy _disambiguationStrategy; + private Options _fromOptions; + private readonly string _locator; + private readonly Options _options; + private readonly string _optionToSelect; + private ElementScope _selectElement; + + internal Select(IDriver driver, + DriverScope scope, + string locator, + string optionToSelect, + DisambiguationStrategy disambiguationStrategy, + Options options) : base(driver, scope, options) + { + _locator = locator; + _optionToSelect = optionToSelect; + _options = options; + _disambiguationStrategy = disambiguationStrategy; + } + + internal Select(IDriver driver, + DriverScope scope, + string locator, + string optionToSelect, + DisambiguationStrategy disambiguationStrategy, + Options options, + Options fromOptions) : this(driver, scope, locator, optionToSelect, disambiguationStrategy, options) + { + _fromOptions = fromOptions; + } + + internal Select(IDriver driver, + ElementScope selectElement, + string optionToSelect, + DisambiguationStrategy disambiguationStrategy, + Options options) : base(driver, selectElement, options) + { + _selectElement = selectElement; + _optionToSelect = optionToSelect; + _options = options; + _disambiguationStrategy = disambiguationStrategy; + } + + public override void Act() + { + _selectElement = _selectElement ?? FindSelectElement(); + SelectOption(_selectElement); + } + + private SnapshotElementScope FindSelectElement() + { + if (_fromOptions == null) _fromOptions = _options; + var selectElementFound = _disambiguationStrategy.ResolveQuery(new SelectFinder(Driver, _locator, Scope, _fromOptions)); + return new SnapshotElementScope(selectElementFound, Scope, _fromOptions); + } + + private void SelectOption(ElementScope selectElementScope) + { + var option = _disambiguationStrategy.ResolveQuery(new OptionFinder(Driver, _optionToSelect, selectElementScope, _options)); + Driver.SelectOption(selectElementScope, option, _optionToSelect); + } + } +} diff --git a/src/Coypu/ActivatorDriverFactory.cs b/src/Coypu/ActivatorDriverFactory.cs index aea77e50..d97561ad 100644 --- a/src/Coypu/ActivatorDriverFactory.cs +++ b/src/Coypu/ActivatorDriverFactory.cs @@ -1,24 +1,24 @@ -using System; -using System.Reflection; - -namespace Coypu -{ - public class ActivatorDriverFactory : DriverFactory - { - public static int OpenDrivers { get; set; } - - public IDriver NewWebDriver(Type driverType, Drivers.Browser browser) - { - try - { - var driver = (IDriver)Activator.CreateInstance(driverType, browser); - OpenDrivers++; - return driver; - } - catch (TargetInvocationException e) - { - throw e.InnerException; - } - } - } -} \ No newline at end of file +using System; +using System.Reflection; + +namespace Coypu +{ + public class ActivatorDriverFactory : DriverFactory + { + public static int OpenDrivers { get; set; } + + public IDriver NewWebDriver(Type driverType, Drivers.Browser browser, bool headless) + { + try + { + var driver = (IDriver)Activator.CreateInstance(driverType, browser, headless); + OpenDrivers++; + return driver; + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + } + } +} diff --git a/src/Coypu/AsyncExtensions.cs b/src/Coypu/AsyncExtensions.cs new file mode 100644 index 00000000..6ee71c2e --- /dev/null +++ b/src/Coypu/AsyncExtensions.cs @@ -0,0 +1,31 @@ +using System.Threading.Tasks; + +namespace Coypu +{ + /// + /// Provides extension methods for working with asynchronous tasks. + /// + public static class AsyncExtensions + { + /// + /// Waits for the task to complete and returns the result. + /// + /// The type of the task result. + /// The task to wait for. + /// The result of the completed task. + public static T Sync(this Task task) + { + task.Wait(); + return task.Result; + } + + /// + /// Waits for the task to complete. + /// + /// The task to wait for. + public static void Sync(this Task task) + { + task.Wait(); + } + } +} diff --git a/src/Coypu/BrowserSession.cs b/src/Coypu/BrowserSession.cs index d20684fe..4d0226fd 100644 --- a/src/Coypu/BrowserSession.cs +++ b/src/Coypu/BrowserSession.cs @@ -1,169 +1,175 @@ -using System; -using Coypu.Finders; -using Coypu.Timing; -using Coypu.WebRequests; - -namespace Coypu -{ - /// - /// A browser session - /// - public class BrowserSession : BrowserWindow, IDisposable - { - private readonly RestrictedResourceDownloader _restrictedResourceDownloader; - - /// - /// A new browser session. Control the lifecycle of this session with using{} / session.Dispose() - /// - /// The new session with default configuration - public BrowserSession() - : this(new SessionConfiguration()) { } - - /// - /// A new browser session. Control the lifecycle of this session with using{} / session.Dispose() - /// - /// configuration for this session - /// The new session - public BrowserSession(SessionConfiguration sessionConfiguration) - : this(sessionConfiguration, - new ActivatorDriverFactory(), - new RetryUntilTimeoutTimingStrategy(), - new StopwatchWaiter(), - new FullyQualifiedUrlBuilder(), - new FinderOptionsDisambiguationStrategy(), - new WebClientWithCookies()) { } - - /// - /// A new browser session with defined driver. - /// Replaces sessionConfiguration driver. - /// - /// - public BrowserSession(IDriver driver) - : this(new SessionConfiguration(), - driver, - new RetryUntilTimeoutTimingStrategy(), - new StopwatchWaiter(), - new FullyQualifiedUrlBuilder(), - new FinderOptionsDisambiguationStrategy(), - new WebClientWithCookies()) { } - - /// - /// A new browser session with defined driver. - /// Replaces sessionConfiguration driver. - /// - /// - /// - public BrowserSession(SessionConfiguration sessionConfiguration, - IDriver driver) - : this(sessionConfiguration, - driver, - new RetryUntilTimeoutTimingStrategy(), - new StopwatchWaiter(), - new FullyQualifiedUrlBuilder(), - new FinderOptionsDisambiguationStrategy(), - new WebClientWithCookies()) { } - - internal BrowserSession(SessionConfiguration sessionConfiguration, - DriverFactory driverFactory, - TimingStrategy timingStrategy, - Waiter waiter, - UrlBuilder urlBuilder, - DisambiguationStrategy disambiguationStrategy, - RestrictedResourceDownloader restrictedResourceDownloader) - : base(sessionConfiguration, - null, - driverFactory.NewWebDriver(sessionConfiguration.Driver, sessionConfiguration.Browser), - timingStrategy, - waiter, - urlBuilder, - disambiguationStrategy) - { - _restrictedResourceDownloader = restrictedResourceDownloader; - } - - internal BrowserSession(SessionConfiguration sessionConfiguration, - IDriver driver, - TimingStrategy timingStrategy, - Waiter waiter, - UrlBuilder urlBuilder, - DisambiguationStrategy disambiguationStrategy, - RestrictedResourceDownloader restrictedResourceDownloader) - : base(sessionConfiguration, - null, - driver, - timingStrategy, - waiter, - urlBuilder, - disambiguationStrategy) - { - _restrictedResourceDownloader = restrictedResourceDownloader; - } - - /// - /// Access to grand-parent DriverScope's driver. - /// - public IDriver Driver => _driver; - - /// - /// The native driver for the Coypu.Driver / browser combination you supplied. E.g. for SeleniumWebDriver + Firefox it - /// will currently be a OpenQA.Selenium.Firefox.FirefoxDriver. - /// - public object Native => _driver.Native; - - internal bool WasDisposed { get; private set; } - - /// - /// Disposes the current session, closing any open browser. - /// - public void Dispose() - { - Dispose(true); - } - - /// - /// Disposes the current session, closing any open browser. - /// Can be overriden with custom implementation. - /// - protected virtual void Dispose(bool disposing) - { - if (WasDisposed) return; - if (disposing) - { - _driver.Dispose(); - ActivatorDriverFactory.OpenDrivers--; - } - - WasDisposed = true; - } - - /// - /// Saves a resource from the web to a local file using the cookies from the current browser session. - /// Allows you to sign in through the browser and then directly download a resource restricted to signed-in users. - /// - /// The location of the resource to download - /// Path to save the file to - public void SaveWebResource(string resource, - string saveAs) - { - _restrictedResourceDownloader.SetCookies(_driver.GetBrowserCookies()); - _restrictedResourceDownloader.DownloadFile(UrlBuilder.GetFullyQualifiedUrl(resource, SessionConfiguration), saveAs); - } - - /// - /// Find an open browser window or tab by its title or name. If no exact match is found a partial match on title will - /// be considered. - /// - /// Window title or name - /// - /// Override the way Coypu is configured to find elements for this call only. - /// E.g. A longer wait: - /// new Options{Timeout = TimeSpan.FromSeconds(60)} - /// - /// The matching BrowserWindow scope - public BrowserWindow FindWindow(string locator, - Options options = null) - { - return new BrowserWindow(new WindowFinder(_driver, locator, this, Merge(options)), this); - } - } -} \ No newline at end of file +using System; +using Coypu.Finders; +using Coypu.Timing; +using Coypu.WebRequests; + +namespace Coypu +{ + /// + /// A browser session + /// + public class BrowserSession : BrowserWindow, IDisposable + { + private readonly RestrictedResourceDownloader _restrictedResourceDownloader; + + /// + /// A new browser session. Control the lifecycle of this session with using{} / session.Dispose() + /// + /// The new session with default configuration + public BrowserSession() + : this(new SessionConfiguration()) { } + + /// + /// A new browser session. Control the lifecycle of this session with using{} / session.Dispose() + /// + /// configuration for this session + /// The new session + public BrowserSession(SessionConfiguration sessionConfiguration) + : this(sessionConfiguration, + new ActivatorDriverFactory(), + new RetryUntilTimeoutTimingStrategy(), + new StopwatchWaiter(), + new FullyQualifiedUrlBuilder(), + new FinderOptionsDisambiguationStrategy(), + new WebClientWithCookies()) { } + + /// + /// A new browser session with defined driver. + /// Replaces sessionConfiguration driver. + /// + /// + public BrowserSession(IDriver driver) + : this(new SessionConfiguration(), + driver, + new RetryUntilTimeoutTimingStrategy(), + new StopwatchWaiter(), + new FullyQualifiedUrlBuilder(), + new FinderOptionsDisambiguationStrategy(), + new WebClientWithCookies()) { } + + /// + /// A new browser session with defined driver. + /// Replaces sessionConfiguration driver. + /// + /// + /// + public BrowserSession(SessionConfiguration sessionConfiguration, + IDriver driver) + : this(sessionConfiguration, + driver, + new RetryUntilTimeoutTimingStrategy(), + new StopwatchWaiter(), + new FullyQualifiedUrlBuilder(), + new FinderOptionsDisambiguationStrategy(), + new WebClientWithCookies()) { } + + internal BrowserSession(SessionConfiguration sessionConfiguration, + DriverFactory driverFactory, + TimingStrategy timingStrategy, + Waiter waiter, + UrlBuilder urlBuilder, + DisambiguationStrategy disambiguationStrategy, + RestrictedResourceDownloader restrictedResourceDownloader) + : base(sessionConfiguration, + null, + driverFactory.NewWebDriver(sessionConfiguration.Driver, sessionConfiguration.Browser, sessionConfiguration.Headless), + timingStrategy, + waiter, + urlBuilder, + disambiguationStrategy) + { + _restrictedResourceDownloader = restrictedResourceDownloader; + } + + internal BrowserSession(SessionConfiguration sessionConfiguration, + IDriver driver, + TimingStrategy timingStrategy, + Waiter waiter, + UrlBuilder urlBuilder, + DisambiguationStrategy disambiguationStrategy, + RestrictedResourceDownloader restrictedResourceDownloader) + : base(sessionConfiguration, + null, + driver, + timingStrategy, + waiter, + urlBuilder, + disambiguationStrategy) + { + _restrictedResourceDownloader = restrictedResourceDownloader; + } + + /// + /// Access to grand-parent DriverScope's driver. + /// + public IDriver Driver => _driver; + + /// + /// The native driver for the Coypu.Driver / browser combination you supplied. E.g. for SeleniumWebDriver + Firefox it + /// will currently be a OpenQA.Selenium.Firefox.FirefoxDriver. + /// + public object Native => _driver.Native; + + internal bool WasDisposed { get; private set; } + + /// + /// Disposes the current session, closing any open browser. + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Disposes the current session, closing any open browser. + /// Can be overriden with custom implementation. + /// + protected virtual void Dispose(bool disposing) + { + if (WasDisposed) return; + if (disposing) + { + _driver.Dispose(); + ActivatorDriverFactory.OpenDrivers--; + } + + WasDisposed = true; + } + + /// + /// Saves a resource from the web to a local file using the cookies from the current browser session. + /// Allows you to sign in through the browser and then directly download a resource restricted to signed-in users. + /// + /// The location of the resource to download + /// Path to save the file to + public void SaveWebResource(string resource, + string saveAs) + { + var cookies = _driver.GetBrowserCookies(); + _restrictedResourceDownloader.SetCookies(cookies); + _restrictedResourceDownloader.DownloadFile(UrlBuilder.GetFullyQualifiedUrl(resource, SessionConfiguration), saveAs); + } + + /// + /// Find an open browser window or tab by its title or name. If no exact match is found a partial match on title will + /// be considered. + /// + /// Window title or name + /// + /// Override the way Coypu is configured to find elements for this call only. + /// E.g. A longer wait: + /// new Options{Timeout = TimeSpan.FromSeconds(60)} + /// + /// The matching BrowserWindow scope + public BrowserWindow FindWindow(string locator, + Options options = null) + { + return new BrowserWindow(new WindowFinder(_driver, locator, this, Merge(options)), this); + } + + /// + /// Manage cookies associated with the current browser session. + /// + public Cookies Cookies => _driver.Cookies; + } +} diff --git a/src/Coypu/BrowserWindow.cs b/src/Coypu/BrowserWindow.cs index ccae91ef..a9bca5f3 100644 --- a/src/Coypu/BrowserWindow.cs +++ b/src/Coypu/BrowserWindow.cs @@ -1,191 +1,237 @@ -using System.Drawing; -using Coypu.Actions; -using Coypu.Finders; -using Coypu.Queries; -using Coypu.Timing; -#pragma warning disable 1591 - -namespace Coypu -{ - /// - /// A browser window belonging to a particular browser session - /// - public class BrowserWindow : DriverScope - { - internal BrowserWindow(SessionConfiguration sessionConfiguration, - ElementFinder elementFinder, - IDriver driver, - TimingStrategy timingStrategy, - Waiter waiter, - UrlBuilder urlBuilder, - DisambiguationStrategy disambiguationStrategy) - : base(sessionConfiguration, elementFinder, driver, timingStrategy, waiter, urlBuilder, - disambiguationStrategy) { } - - internal BrowserWindow(ElementFinder elementFinder, - DriverScope outerScope) : base(elementFinder, outerScope) { } - - /// - /// Returns the page's title displayed in the browser - /// - public string Title => _driver.Title(this); - - internal override bool Stale { get; set; } - - /// - /// Check that a dialog with the specified text appears within the - /// SessionConfiguration.Timeout - /// - /// - /// Dialog text - /// - /// Whether an element appears - public bool HasDialog(string withText, - Options options = null) - { - return Query(new HasDialogQuery(_driver, withText, this, Merge(options))); - } - - /// - /// Check that a dialog with the specified is not present. Returns as soon as the dialog is not present, or when the - /// - /// SessionConfiguration.Timeout - /// - /// is reached. - /// - /// Dialog text - /// - /// Whether an element does not appears - public bool HasNoDialog(string withText, - Options options = null) - { - return Query(new HasNoDialogQuery(_driver, withText, this, Merge(options))); - } - - /// - /// Accept the first modal dialog to appear within the - /// SessionConfiguration.Timeout - /// - /// - /// Thrown if the dialog cannot be found - public void AcceptModalDialog(Options options = null) - { - RetryUntilTimeout(new AcceptModalDialog(this, _driver, Merge(options))); - } - - /// - /// Cancel the first modal dialog to appear within the - /// SessionConfiguration.Timeout - /// - /// - /// Thrown if the dialog cannot be found - public void CancelModalDialog(Options options = null) - { - RetryUntilTimeout(new CancelModalDialog(this, _driver, Merge(options))); - } - - /// - /// Visit a url in the browser - /// - /// - /// Virtual paths will use the SessionConfiguration.AppHost,Port,SSL settings. Otherwise supply a - /// fully qualified URL. - /// - public void Visit(string virtualPath) - { - _driver.Visit(UrlBuilder.GetFullyQualifiedUrl(virtualPath, SessionConfiguration), this); - } - - /// - /// Navigate back to the previous location in the browser's history - /// - public void GoBack() - { - _driver.GoBack(this); - } - - /// - /// Navigate forward to the next location in the browser's history - /// - public void GoForward() - { - _driver.GoForward(this); - } - - /// - /// Executes custom javascript in the browser - /// - /// JavaScript to execute - /// Script arguments to be passed down to the browser - /// Anything returned from the script - public object ExecuteScript(string javascript, - params object[] args) - { - return _driver.ExecuteScript(javascript, this, args); - } - - /// - /// Maximises this browser window - /// - public void MaximiseWindow() - { - _driver.MaximiseWindow(this); - } - - /// - /// Resizes this browser window to the supplied dimensions - /// - /// The required width - /// The required height - public void ResizeTo(int width, - int height) - { - _driver.ResizeTo(new Size(width, height), this); - } - - /// - /// Refreshes the current browser window page - /// - public void Refresh() - { - _driver.Refresh(this); - } - - public void SaveScreenshot(string saveAs) - { - _driver.SaveScreenshot(saveAs, this); - } - - /// - /// Check if this window exists within the - /// SessionConfiguration.Timeout - /// - /// - /// - /// - /// Override the way Coypu is configured to find elements for this call only. - /// E.g. A longer wait: - /// new Options{Timeout = TimeSpan.FromSeconds(60)} - /// - public bool Exists(Options options = null) - { - return Query(new WindowExistsQuery(this, Merge(options))); - } - - /// - /// Check if this window becomes missing within the - /// SessionConfiguration.Timeout - /// - /// - /// - /// - /// Override the way Coypu is configured to find elements for this call only. - /// E.g. A longer wait: - /// new Options{Timeout = TimeSpan.FromSeconds(60)} - /// - public bool Missing(Options options = null) - { - return Query(new WindowMissingQuery(this, Merge(options))); - } - } -} \ No newline at end of file +using System; +using System.Drawing; +using Coypu.Actions; +using Coypu.Finders; +using Coypu.Queries; +using Coypu.Timing; +#pragma warning disable 1591 + +namespace Coypu +{ + /// + /// A browser window belonging to a particular browser session + /// + public class BrowserWindow : DriverScope + { + internal BrowserWindow(SessionConfiguration sessionConfiguration, + ElementFinder elementFinder, + IDriver driver, + TimingStrategy timingStrategy, + Waiter waiter, + UrlBuilder urlBuilder, + DisambiguationStrategy disambiguationStrategy) + : base(sessionConfiguration, elementFinder, driver, timingStrategy, waiter, urlBuilder, + disambiguationStrategy) { } + + internal BrowserWindow(ElementFinder elementFinder, + DriverScope outerScope) : base(elementFinder, outerScope) { } + + /// + /// Returns the page's title displayed in the browser + /// + public string Title => _driver.Title(this); + + internal override bool Stale { get; set; } + + /// + /// Check that a dialog with the specified text appears within the + /// SessionConfiguration.Timeout + /// + /// + /// Dialog text + /// + /// Whether an element appears + public bool HasDialog(string withText, + Options options = null) + { + return Query(new HasDialogQuery(_driver, withText, this, Merge(options))); + } + + /// + /// Check that a dialog with the specified is not present. Returns as soon as the dialog is not present, or when the + /// + /// SessionConfiguration.Timeout + /// + /// is reached. + /// + /// Dialog text + /// + /// Whether an element does not appears + public bool HasNoDialog(string withText, + Options options = null) + { + return Query(new HasNoDialogQuery(_driver, withText, this, Merge(options))); + } + + /// + /// Accept the first modal dialog to appear within the + /// SessionConfiguration.Timeout + /// + /// + /// Thrown if the dialog cannot be found + public void AcceptModalDialog(Options options = null) + { + RetryUntilTimeout(new AcceptModalDialog(this, _driver, Merge(options))); + } + + /// + /// Cancel the first modal dialog to appear within the + /// SessionConfiguration.Timeout + /// + /// + /// Thrown if the dialog cannot be found + public void CancelModalDialog(Options options = null) + { + RetryUntilTimeout(new CancelModalDialog(this, _driver, Merge(options))); + } + + public void AcceptAlert(Action trigger) + { + AcceptAlert(null, trigger); + } + + public void AcceptAlert(string text, Action trigger) + { + _driver.AcceptAlert(text, this, trigger); + } + + public void AcceptConfirm(Action trigger) + { + AcceptConfirm(null, trigger); + } + + public void AcceptConfirm(string text, Action trigger) + { + _driver.AcceptConfirm(text, this, trigger); + } + + public void CancelConfirm(Action trigger) + { + CancelConfirm(null, trigger); + } + + public void CancelConfirm(string text, Action trigger) + { + _driver.CancelConfirm(text, this, trigger); + } + + public void AcceptPrompt(string promptValue, Action trigger) + { + AcceptPrompt(null, promptValue, trigger); + } + + public void AcceptPrompt(string text, string promptValue, Action trigger) + { + _driver.AcceptPrompt(text, promptValue, this, trigger); + } + + public void CancelPrompt(string text, DriverScope root, Action trigger) + { + _driver.CancelPrompt(text, this, trigger); + } + + /// + /// Visit a url in the browser + /// + /// + /// Virtual paths will use the SessionConfiguration.AppHost,Port,SSL settings. Otherwise supply a + /// fully qualified URL. + /// + public void Visit(string virtualPath) + { + _driver.Visit(UrlBuilder.GetFullyQualifiedUrl(virtualPath, SessionConfiguration), this); + } + + /// + /// Navigate back to the previous location in the browser's history + /// + public void GoBack() + { + _driver.GoBack(this); + } + + /// + /// Navigate forward to the next location in the browser's history + /// + public void GoForward() + { + _driver.GoForward(this); + } + + /// + /// Executes custom javascript in the browser + /// + /// JavaScript to execute + /// Script arguments to be passed down to the browser + /// Anything returned from the script + public object ExecuteScript(string javascript, + params object[] args) + { + return _driver.ExecuteScript(javascript, this, args); + } + + /// + /// Maximises this browser window + /// + public void MaximiseWindow() + { + _driver.MaximiseWindow(this); + } + + /// + /// Resizes this browser window to the supplied dimensions + /// + /// The required width + /// The required height + public void ResizeTo(int width, + int height) + { + _driver.ResizeTo(new Size(width, height), this); + } + + /// + /// Refreshes the current browser window page + /// + public void Refresh() + { + _driver.Refresh(this); + } + + public void SaveScreenshot(string saveAs) + { + _driver.SaveScreenshot(saveAs, this); + } + + /// + /// Check if this window exists within the + /// SessionConfiguration.Timeout + /// + /// + /// + /// + /// Override the way Coypu is configured to find elements for this call only. + /// E.g. A longer wait: + /// new Options{Timeout = TimeSpan.FromSeconds(60)} + /// + public bool Exists(Options options = null) + { + return Query(new WindowExistsQuery(this, Merge(options))); + } + + /// + /// Check if this window becomes missing within the + /// SessionConfiguration.Timeout + /// + /// + /// + /// + /// Override the way Coypu is configured to find elements for this call only. + /// E.g. A longer wait: + /// new Options{Timeout = TimeSpan.FromSeconds(60)} + /// + public bool Missing(Options options = null) + { + return Query(new WindowMissingQuery(this, Merge(options))); + } + } +} diff --git a/src/Coypu/Cookies.cs b/src/Coypu/Cookies.cs index 67aa9adb..8023ea05 100644 --- a/src/Coypu/Cookies.cs +++ b/src/Coypu/Cookies.cs @@ -1,124 +1,16 @@ -using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using OpenQA.Selenium; +using System.Net; namespace Coypu { - public class Cookies + public interface Cookies { - private readonly IWebDriver _nativeDriver; - - public Cookies(IWebDriver nativeDriver) - { - _nativeDriver = nativeDriver; - } - - public void AddCookie(Cookie cookie, - Options options = null) - { - try - { - _nativeDriver.Manage() - .Cookies.AddCookie(cookie); - _nativeDriver.Navigate() - .Refresh(); - WaitUntilCookieExists(cookie, options); - } - catch (Exception e) - { - Console.WriteLine($"\t-> Could not attach the cookie {cookie.Name} to the browser session. {e.Message}"); - } - } - - public void DeleteAll() - { - try - { - _nativeDriver.Manage() - .Cookies.DeleteAllCookies(); - _nativeDriver.Navigate() - .Refresh(); - } - catch (Exception e) - { - Console.WriteLine($"\t-> Could not delete the cookies from the browser session. {e.Message}"); - } - } - - public void DeleteCookie(Cookie cookie) - { - try - { - _nativeDriver.Manage() - .Cookies.DeleteCookie(cookie); - _nativeDriver.Navigate() - .Refresh(); - } - catch (Exception e) - { - Console.WriteLine($"\t-> Could not delete the cookie name '{cookie.Name}' from the browser session. {e.Message}"); - } - } - - public void DeleteCookieNamed(string cookieName) - { - try - { - _nativeDriver.Manage() - .Cookies.DeleteCookieNamed(cookieName); - _nativeDriver.Navigate() - .Refresh(); - } - catch (Exception e) - { - Console.WriteLine($"\t-> Could not delete the cookie by name '{cookieName}' from the browser session. {e.Message}"); - } - } - - public IEnumerable GetAll() - { - return _nativeDriver.Manage() - .Cookies.AllCookies.Select(c => new Cookie(c.Name, c.Value, c.Domain, c.Path, DateTime.MaxValue)); - } - - public Cookie GetCookieNamed(string cookieName) - { - var cookie = _nativeDriver.Manage() - .Cookies.GetCookieNamed(cookieName); - if (cookie == null) Console.WriteLine($"\t-> Could not get cookie by name '{cookieName}' from the browser session."); - return cookie; - } - - public void WaitUntilCookieExists(Cookie cookie, - Options options) - { - var allCookies = _nativeDriver.Manage() - .Cookies.AllCookies; - var stopWatch = Stopwatch.StartNew(); - try - { - while (stopWatch.ElapsedMilliseconds < options.Timeout.TotalMilliseconds) - { - if (allCookies.Any(x => x.Name.Trim() == cookie.Name)) - { - Console.WriteLine($"\t-> Cookie name '{cookie.Name}' exists."); - break; - } - - allCookies = _nativeDriver.Manage() - .Cookies.AllCookies; - Thread.Sleep(options.RetryInterval); - } - - stopWatch.Stop(); - } - catch (Exception e) - { - Console.WriteLine($"\t-> Cookie name '{cookie.Name}' does NOT exist. After {stopWatch.Elapsed.TotalSeconds} seconds. {e.Message}"); - } - } + public void AddCookie(Cookie cookie, Options options = null); + public void DeleteAll(); + public void DeleteCookie(Cookie cookie); + public void DeleteCookieNamed(string cookieName); + public IEnumerable GetAll(); + public Cookie GetCookieNamed(string cookieName); + public void WaitUntilCookieExists(Cookie cookie, Options options); } -} \ No newline at end of file +} diff --git a/src/Coypu/Coypu.csproj b/src/Coypu/Coypu.csproj index c844823a..eeba19d1 100644 --- a/src/Coypu/Coypu.csproj +++ b/src/Coypu/Coypu.csproj @@ -4,10 +4,10 @@ net6.0 true Coypu - 4.0.0.0 - 4.0.0.0 + 4.1.0.0 + 4.1.0.0 Coypu - 4.0.0 + 4.1.0 Intuitive, robust browser automation for the .NET platform. This package includes the Coypu 4 framework assembly, which should be referenced by your tests. @@ -34,8 +34,10 @@ Supported platforms: full bin\$(Configuration)\ bin\Debug\Coypu.xml + true + diff --git a/src/Coypu/DriverFactory.cs b/src/Coypu/DriverFactory.cs index 2a6059fa..efd1c896 100644 --- a/src/Coypu/DriverFactory.cs +++ b/src/Coypu/DriverFactory.cs @@ -1,9 +1,9 @@ -using System; - -namespace Coypu -{ - public interface DriverFactory - { - IDriver NewWebDriver(Type driverType, Drivers.Browser browser); - } -} \ No newline at end of file +using System; + +namespace Coypu +{ + public interface DriverFactory + { + IDriver NewWebDriver(Type driverType, Drivers.Browser browser, bool headless); + } +} diff --git a/src/Coypu/DriverScope.cs b/src/Coypu/DriverScope.cs index 6b584069..ed9780ef 100644 --- a/src/Coypu/DriverScope.cs +++ b/src/Coypu/DriverScope.cs @@ -1,387 +1,387 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Coypu.Actions; -using Coypu.Drivers; -using Coypu.Finders; -using Coypu.Queries; -using Coypu.Timing; - -// ReSharper disable InconsistentNaming -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace Coypu -{ - public abstract class DriverScope : Scope - { - protected readonly DisambiguationStrategy DisambiguationStrategy; - protected readonly SessionConfiguration SessionConfiguration; - protected TimingStrategy TimingStrategy; - protected readonly Waiter Waiter; - internal IDriver _driver; - internal readonly ElementFinder _elementFinder; - internal StateFinder StateFinder; - internal UrlBuilder UrlBuilder; - private Element _element; - - internal DriverScope(SessionConfiguration sessionConfiguration, - ElementFinder elementFinder, - IDriver driver, - TimingStrategy timingStrategy, - Waiter waiter, - UrlBuilder urlBuilder, - DisambiguationStrategy disambiguationStrategy) - { - _driver = driver; - _elementFinder = elementFinder ?? new DocumentElementFinder(driver, sessionConfiguration); - DisambiguationStrategy = disambiguationStrategy; - SessionConfiguration = sessionConfiguration; - StateFinder = new StateFinder(timingStrategy); - TimingStrategy = timingStrategy; - UrlBuilder = urlBuilder; - Waiter = waiter; - } - - internal DriverScope(ElementFinder elementFinder, - DriverScope outerScope) - { - _driver = outerScope._driver; - _elementFinder = elementFinder; - DisambiguationStrategy = outerScope.DisambiguationStrategy; - OuterScope = outerScope; - SessionConfiguration = outerScope.SessionConfiguration; - StateFinder = outerScope.StateFinder; - TimingStrategy = outerScope.TimingStrategy; - UrlBuilder = outerScope.UrlBuilder; - Waiter = outerScope.Waiter; - } - - public ElementFinder ElementFinder => _elementFinder; - - internal abstract bool Stale { get; set; } - - public string Text => Now().Text; - - public DriverScope OuterScope { get; } - - public virtual Uri Location => _driver.Location(this); - - public Browser Browser => SessionConfiguration.Browser; - - public void ClickButton(string locator, - Options options = null) - { - RetryUntilTimeout(WaitThenClickButton(locator, Merge(options))); - } - - public void ClickLink(string locator, - Options options = null) - { - RetryUntilTimeout(WaitThenClickLink(locator, Merge(options))); - } - - public Scope ClickButton(string locator, - PredicateQuery until, - Options options = null) - { - TryUntil(WaitThenClickButton(locator, Merge(options)), until, Merge(options)); - return this; - } - - public Scope ClickLink(string locator, - PredicateQuery until, - Options options = null) - { - TryUntil(WaitThenClickLink(locator, Merge(options)), until, Merge(options)); - return this; - } - - public ElementScope FindButton(string locator, - Options options = null) - { - return new ButtonFinder(_driver, locator, this, Merge(options)).AsScope(); - } - - public ElementScope FindLink(string locator, - Options options = null) - { - return new LinkFinder(_driver, locator, this, Merge(options)).AsScope(); - } - - public ElementScope FindField(string locator, - Options options = null) - { - return new FieldFinder(_driver, locator, this, Merge(options)).AsScope(); - } - - public FillInWith FillIn(string locator, - Options options = null) - { - return new FillInWith(FindField(locator, options), _driver, TimingStrategy, Merge(options)); - } - - public SelectFrom Select(string option, - Options options = null) - { - return new SelectFrom(option, _driver, TimingStrategy, this, Merge(options), DisambiguationStrategy); - } - - public bool HasContent(string text, - Options options = null) - { - return Query(new HasContentQuery(this, text, Merge(options))); - } - - public bool HasContentMatch(Regex pattern, - Options options = null) - { - return Query(new HasContentMatchQuery(this, pattern, Merge(options))); - } - - public bool HasNoContent(string text, - Options options = null) - { - return Query(new HasNoContentQuery(this, text, Merge(options))); - } - - public bool HasNoContentMatch(Regex pattern, - Options options = null) - { - return Query(new HasNoContentMatchQuery(this, pattern, Merge(options))); - } - - public ElementScope FindCss(string cssSelector, - Options options = null) - { - return new CssFinder(_driver, cssSelector, this, Merge(options)).AsScope(); - } - - public ElementScope FindCss(string cssSelector, - string text, - Options options = null) - { - return new CssFinder(_driver, cssSelector, this, Merge(options), text).AsScope(); - } - - public ElementScope FindCss(string cssSelector, - Regex text, - Options options = null) - { - return new CssFinder(_driver, cssSelector, this, Merge(options), text).AsScope(); - } - - public ElementScope FindXPath(string xpath, - Options options = null) - { - return new XPathFinder(_driver, xpath, this, Merge(options)).AsScope(); - } - - public ElementScope FindXPath(string xpath, - string text, - Options options = null) - { - return new XPathFinder(_driver, xpath, this, Merge(options), text).AsScope(); - } - - public ElementScope FindXPath(string xpath, - Regex text, - Options options = null) - { - return new XPathFinder(_driver, xpath, this, Merge(options), text).AsScope(); - } - - public IEnumerable FindAllCss(string cssSelector, - Func, bool> predicate = null, - Options options = null) - { - return Query(new FindAllCssWithPredicateQuery(cssSelector, predicate, this, Merge(options))); - } - - public IEnumerable FindAllXPath(string xpath, - Func, bool> predicate = null, - Options options = null) - { - return Query(new FindAllXPathWithPredicateQuery(xpath, predicate, this, Merge(options))); - } - - public ElementScope FindSection(string locator, - Options options = null) - { - return new SectionFinder(_driver, locator, this, Merge(options)).AsScope(); - } - - public ElementScope FindFieldset(string locator, - Options options = null) - { - return new FieldsetFinder(_driver, locator, this, Merge(options)).AsScope(); - } - - public ElementScope FindId(string id, - Options options = null) - { - return new IdFinder(_driver, id, this, Merge(options)).AsScope(); - } - - public ElementScope FindIdEndingWith(string endsWith, - Options options = null) - { - return FindCss(string.Format(@"*[id$=""{0}""]", endsWith), options); - } - - public void Check(string locator, - Options options = null) - { - RetryUntilTimeout(new CheckAction(_driver, FindField(locator, options), Merge(options))); - } - - public void Uncheck(string locator, - Options options = null) - { - RetryUntilTimeout(new Uncheck(_driver, FindField(locator, options), Merge(options))); - } - - public void Choose(string locator, - Options options = null) - { - RetryUntilTimeout(new Choose(_driver, FindField(locator, options), Merge(options))); - } - - public void RetryUntilTimeout(Action action, - Options options = null) - { - TimingStrategy.Synchronise(new LambdaBrowserAction(action, Merge(options))); - } - - public TResult RetryUntilTimeout(Func function, - Options options = null) - { - return TimingStrategy.Synchronise(new LambdaQuery(function, Merge(options))); - } - - public void RetryUntilTimeout(BrowserAction action) - { - Query(action); - } - - public ElementScope FindFrame(string locator, - Options options = null) - { - return new FrameFinder(_driver, locator, this, Merge(options)).AsScope(); - } - - public T Query(Func query, - T expecting, - Options options = null) - { - return TimingStrategy.Synchronise(new LambdaQuery(query, expecting, Merge(options))); - } - - public T Query(Query query) - { - return TimingStrategy.Synchronise(query); - } - - public void TryUntil(Action tryThis, - Func until, - TimeSpan waitBeforeRetry, - Options options = null) - { - var mergedOptions = Merge(options); - var predicateOptions = Options.Merge(new Options {Timeout = waitBeforeRetry}, mergedOptions); - - TimingStrategy.TryUntil(new LambdaBrowserAction(tryThis, mergedOptions), - new LambdaPredicateQuery(WithZeroTimeout(until), predicateOptions), - mergedOptions); - } - - public void TryUntil(BrowserAction tryThis, - PredicateQuery until, - Options options = null) - { - TimingStrategy.TryUntil(tryThis, until, Merge(options)); - } - - public State FindState(State[] states, - Options options) - { - return StateFinder.FindState(states, this, Merge(options)); - } - - public State FindState(params State[] states) - { - return FindState(states, null); - } - - /// - /// Try and find this scope now - /// - /// - /// Thrown if the element cannot be found - /// - /// Thrown if the there is more than one matching element and the - /// Match.Single option is set - /// - public virtual Element Now() - { - return FindElement(); - } - - protected internal virtual Element FindElement() - { - if (_element == null || Stale) - _element = DisambiguationStrategy.ResolveQuery(ElementFinder); - return _element; - } - - internal Options Merge(Options options) - { - var mergeWith = ElementFinder != null - ? ElementFinder.Options - : SessionConfiguration; - return Options.Merge(options, mergeWith); - } - - internal IEnumerable FindAllCssNoPredicate(string cssSelector, - Options options) - { - return _driver.FindAllCss(cssSelector, this, options) - .AsSnapshotElementScopes(this, options); - } - - internal IEnumerable FindAllXPathNoPredicate(string xpath, - Options options) - { - return _driver.FindAllXPath(xpath, this, options) - .AsSnapshotElementScopes(this, options); - } - - private WaitThenClick WaitThenClickLink(string locator, - Options options = null) - { - return new WaitThenClick(_driver, this, Merge(options), Waiter, new LinkFinder(_driver, locator, this, Merge(options)), DisambiguationStrategy); - } - - private WaitThenClick WaitThenClickButton(string locator, - Options options = null) - { - return new WaitThenClick(_driver, this, Merge(options), Waiter, new ButtonFinder(_driver, locator, this, Merge(options)), DisambiguationStrategy); - } - - private Func WithZeroTimeout(Func query) - { - var zeroTimeoutUntil = new Func(() => - { - var was = TimingStrategy.ZeroTimeout; - TimingStrategy.ZeroTimeout = true; - try - { - return query(); - } - finally - { - TimingStrategy.ZeroTimeout = was; - } - }); - return zeroTimeoutUntil; - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Coypu.Actions; +using Coypu.Drivers; +using Coypu.Finders; +using Coypu.Queries; +using Coypu.Timing; + +// ReSharper disable InconsistentNaming +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Coypu +{ + public abstract class DriverScope : Scope + { + protected readonly DisambiguationStrategy DisambiguationStrategy; + protected readonly SessionConfiguration SessionConfiguration; + protected TimingStrategy TimingStrategy; + protected readonly Waiter Waiter; + internal IDriver _driver; + internal readonly ElementFinder _elementFinder; + internal StateFinder StateFinder; + internal UrlBuilder UrlBuilder; + private Element _element; + + internal DriverScope(SessionConfiguration sessionConfiguration, + ElementFinder elementFinder, + IDriver driver, + TimingStrategy timingStrategy, + Waiter waiter, + UrlBuilder urlBuilder, + DisambiguationStrategy disambiguationStrategy) + { + _driver = driver; + _elementFinder = elementFinder ?? new DocumentElementFinder(driver, sessionConfiguration); + DisambiguationStrategy = disambiguationStrategy; + SessionConfiguration = sessionConfiguration; + StateFinder = new StateFinder(timingStrategy); + TimingStrategy = timingStrategy; + UrlBuilder = urlBuilder; + Waiter = waiter; + } + + internal DriverScope(ElementFinder elementFinder, + DriverScope outerScope) + { + _driver = outerScope._driver; + _elementFinder = elementFinder; + DisambiguationStrategy = outerScope.DisambiguationStrategy; + OuterScope = outerScope; + SessionConfiguration = outerScope.SessionConfiguration; + StateFinder = outerScope.StateFinder; + TimingStrategy = outerScope.TimingStrategy; + UrlBuilder = outerScope.UrlBuilder; + Waiter = outerScope.Waiter; + } + + public ElementFinder ElementFinder => _elementFinder; + + internal abstract bool Stale { get; set; } + + public string Text => Now().Text; + + public DriverScope OuterScope { get; } + + public virtual Uri Location => _driver.Location(this); + + public Browser Browser => SessionConfiguration.Browser; + + public void ClickButton(string locator, + Options options = null) + { + RetryUntilTimeout(WaitThenClickButton(locator, Merge(options))); + } + + public void ClickLink(string locator, + Options options = null) + { + RetryUntilTimeout(WaitThenClickLink(locator, Merge(options))); + } + + public Scope ClickButton(string locator, + PredicateQuery until, + Options options = null) + { + TryUntil(WaitThenClickButton(locator, Merge(options)), until, Merge(options)); + return this; + } + + public Scope ClickLink(string locator, + PredicateQuery until, + Options options = null) + { + TryUntil(WaitThenClickLink(locator, Merge(options)), until, Merge(options)); + return this; + } + + public ElementScope FindButton(string locator, + Options options = null) + { + return new ButtonFinder(_driver, locator, this, Merge(options)).AsScope(); + } + + public ElementScope FindLink(string locator, + Options options = null) + { + return new LinkFinder(_driver, locator, this, Merge(options)).AsScope(); + } + + public ElementScope FindField(string locator, + Options options = null) + { + return new FieldFinder(_driver, locator, this, Merge(options)).AsScope(); + } + + public FillInWith FillIn(string locator, + Options options = null) + { + return new FillInWith(FindField(locator, options), _driver, TimingStrategy, Merge(options)); + } + + public SelectFrom Select(string option, + Options options = null) + { + return new SelectFrom(option, _driver, TimingStrategy, this, Merge(options), DisambiguationStrategy); + } + + public bool HasContent(string text, + Options options = null) + { + return Query(new HasContentQuery(this, text, Merge(options))); + } + + public bool HasContentMatch(Regex pattern, + Options options = null) + { + return Query(new HasContentMatchQuery(this, pattern, Merge(options))); + } + + public bool HasNoContent(string text, + Options options = null) + { + return Query(new HasNoContentQuery(this, text, Merge(options))); + } + + public bool HasNoContentMatch(Regex pattern, + Options options = null) + { + return Query(new HasNoContentMatchQuery(this, pattern, Merge(options))); + } + + public ElementScope FindCss(string cssSelector, + Options options = null) + { + return new CssFinder(_driver, cssSelector, this, Merge(options)).AsScope(); + } + + public ElementScope FindCss(string cssSelector, + string text, + Options options = null) + { + return new CssFinder(_driver, cssSelector, this, Merge(options), text).AsScope(); + } + + public ElementScope FindCss(string cssSelector, + Regex text, + Options options = null) + { + return new CssFinder(_driver, cssSelector, this, Merge(options), text).AsScope(); + } + + public ElementScope FindXPath(string xpath, + Options options = null) + { + return new XPathFinder(_driver, xpath, this, Merge(options)).AsScope(); + } + + public ElementScope FindXPath(string xpath, + string text, + Options options = null) + { + return new XPathFinder(_driver, xpath, this, Merge(options), text).AsScope(); + } + + public ElementScope FindXPath(string xpath, + Regex text, + Options options = null) + { + return new XPathFinder(_driver, xpath, this, Merge(options), text).AsScope(); + } + + public IEnumerable FindAllCss(string cssSelector, + Func, bool> predicate = null, + Options options = null) + { + return Query(new FindAllCssWithPredicateQuery(cssSelector, predicate, this, Merge(options))); + } + + public IEnumerable FindAllXPath(string xpath, + Func, bool> predicate = null, + Options options = null) + { + return Query(new FindAllXPathWithPredicateQuery(xpath, predicate, this, Merge(options))); + } + + public ElementScope FindSection(string locator, + Options options = null) + { + return new SectionFinder(_driver, locator, this, Merge(options)).AsScope(); + } + + public ElementScope FindFieldset(string locator, + Options options = null) + { + return new FieldsetFinder(_driver, locator, this, Merge(options)).AsScope(); + } + + public ElementScope FindId(string id, + Options options = null) + { + return new IdFinder(_driver, id, this, Merge(options)).AsScope(); + } + + public ElementScope FindIdEndingWith(string endsWith, + Options options = null) + { + return FindCss(string.Format(@"*[id$=""{0}""]", endsWith), options); + } + + public void Check(string locator, + Options options = null) + { + RetryUntilTimeout(new CheckAction(_driver, FindField(locator, options), Merge(options))); + } + + public void Uncheck(string locator, + Options options = null) + { + RetryUntilTimeout(new Uncheck(_driver, FindField(locator, options), Merge(options))); + } + + public void Choose(string locator, + Options options = null) + { + RetryUntilTimeout(new Choose(_driver, FindField(locator, options), Merge(options))); + } + + public void RetryUntilTimeout(Action action, + Options options = null) + { + TimingStrategy.Synchronise(new LambdaBrowserAction(action, Merge(options))); + } + + public TResult RetryUntilTimeout(Func function, + Options options = null) + { + return TimingStrategy.Synchronise(new LambdaQuery(function, Merge(options))); + } + + public void RetryUntilTimeout(BrowserAction action) + { + Query(action); + } + + public ElementScope FindFrame(string locator, + Options options = null) + { + return new FrameFinder(_driver, locator, this, Merge(options)).AsScope(); + } + + public T Query(Func query, + T expecting, + Options options = null) + { + return TimingStrategy.Synchronise(new LambdaQuery(query, expecting, Merge(options))); + } + + public T Query(Query query) + { + return TimingStrategy.Synchronise(query); + } + + public void TryUntil(Action tryThis, + Func until, + TimeSpan waitBeforeRetry, + Options options = null) + { + var mergedOptions = Merge(options); + var predicateOptions = Options.Merge(new Options {Timeout = waitBeforeRetry}, mergedOptions); + + TimingStrategy.TryUntil(new LambdaBrowserAction(tryThis, mergedOptions), + new LambdaPredicateQuery(WithZeroTimeout(until), predicateOptions), + mergedOptions); + } + + public void TryUntil(BrowserAction tryThis, + PredicateQuery until, + Options options = null) + { + TimingStrategy.TryUntil(tryThis, until, Merge(options)); + } + + public State FindState(State[] states, + Options options) + { + return StateFinder.FindState(states, this, Merge(options)); + } + + public State FindState(params State[] states) + { + return FindState(states, null); + } + + /// + /// Try and find this scope now + /// + /// + /// Thrown if the element cannot be found + /// + /// Thrown if the there is more than one matching element and the + /// Match.Single option is set + /// + public virtual Element Now() + { + return FindElement(); + } + + protected internal virtual Element FindElement() + { + if (_element == null || Stale) + _element = DisambiguationStrategy.ResolveQuery(ElementFinder); + return _element; + } + + internal Options Merge(Options options) + { + var mergeWith = ElementFinder != null + ? ElementFinder.Options + : SessionConfiguration; + return Options.Merge(options, mergeWith); + } + + internal IEnumerable FindAllCssNoPredicate(string cssSelector, + Options options) + { + return _driver.FindAllCss(cssSelector, this, options) + .AsSnapshotElementScopes(this, options); + } + + internal IEnumerable FindAllXPathNoPredicate(string xpath, + Options options) + { + return _driver.FindAllXPath(xpath, this, options) + .AsSnapshotElementScopes(this, options); + } + + private WaitThenClick WaitThenClickLink(string locator, + Options options = null) + { + return new WaitThenClick(_driver, this, Merge(options), Waiter, new LinkFinder(_driver, locator, this, Merge(options)), DisambiguationStrategy); + } + + private WaitThenClick WaitThenClickButton(string locator, + Options options = null) + { + return new WaitThenClick(_driver, this, Merge(options), Waiter, new ButtonFinder(_driver, locator, this, Merge(options)), DisambiguationStrategy); + } + + private Func WithZeroTimeout(Func query) + { + var zeroTimeoutUntil = new Func(() => + { + var was = TimingStrategy.ZeroTimeout; + TimingStrategy.ZeroTimeout = true; + try + { + return query(); + } + finally + { + TimingStrategy.ZeroTimeout = was; + } + }); + return zeroTimeoutUntil; + } + } +} diff --git a/src/Coypu/Drivers/Browser.cs b/src/Coypu/Drivers/Browser.cs index 423f5e7a..b75c6eef 100644 --- a/src/Coypu/Drivers/Browser.cs +++ b/src/Coypu/Drivers/Browser.cs @@ -1,46 +1,53 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -#pragma warning disable 1591 - -namespace Coypu.Drivers -{ - /// - /// The browser that will be used by your chosen driver - /// - public class Browser - { - public static Browser Firefox = new Browser - { - Javascript = true, - UppercaseTagNames = true - }; - - public static Browser InternetExplorer = new Browser {Javascript = true}; - public static Browser Chrome = new Browser {Javascript = true}; - public static Browser Edge = new Browser {Javascript = true}; - public static Browser Opera = new Browser {Javascript = true}; - public static Browser Safari = new Browser {Javascript = true}; - private Browser() { } - - public bool Javascript { get; private set; } - public bool UppercaseTagNames { get; private set; } - - public static Browser Parse(string browserName) - { - var fieldInfo = BrowserFields() - .FirstOrDefault(f => f.Name.Equals(browserName.Replace(" ", ""), - StringComparison.InvariantCultureIgnoreCase)); - if (fieldInfo == null) - throw new NoSuchBrowserException(browserName); - return (Browser) fieldInfo.GetValue(null); - } - - private static IEnumerable BrowserFields() - { - return typeof(Browser).GetFields(BindingFlags.Public | BindingFlags.Static); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +#pragma warning disable 1591 + +namespace Coypu.Drivers +{ + /// + /// The browser that will be used by your chosen driver + /// + public class Browser + { + public static Browser Firefox = new Browser + { + Javascript = true, + UppercaseTagNames = true + }; + + public static Browser InternetExplorer = new Browser {Javascript = true, Name = "InternetExplorer"}; + public static Browser Chrome = new Browser {Javascript = true, Name = "Chrome"}; + public static Browser Chromium = new Browser {Javascript = true, Name = "Chromium"}; + public static Browser Edge = new Browser {Javascript = true, Name = "Edge"}; + public static Browser Opera = new Browser {Javascript = true, Name = "Opera"}; + public static Browser Safari = new Browser {Javascript = true, Name = "Safari"}; + public static Browser Webkit = new Browser {Javascript = true, Name = "Webkit"}; + private Browser() { } + + public bool Javascript { get; private set; } + + public string Name { get; private set; } + public bool UppercaseTagNames { get; private set; } + + public static Browser Parse(string browserName) + { + var fieldInfo = BrowserFields() + .FirstOrDefault(f => f.Name.Equals(browserName.Replace(" ", ""), + StringComparison.InvariantCultureIgnoreCase)); + if (fieldInfo == null) + throw new NoSuchBrowserException(browserName); + return (Browser) fieldInfo.GetValue(null); + } + + public override string ToString() => Name; + + private static IEnumerable BrowserFields() + { + return typeof(Browser).GetFields(BindingFlags.Public | BindingFlags.Static); + } + + } +} diff --git a/src/Coypu/Drivers/Playwright/Cookies.cs b/src/Coypu/Drivers/Playwright/Cookies.cs new file mode 100644 index 00000000..3aa01682 --- /dev/null +++ b/src/Coypu/Drivers/Playwright/Cookies.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.Playwright; + +namespace Coypu.Drivers.Playwright +{ + // Implement the interface using a PlaywrightContext + public class Cookies : Coypu.Cookies + { + private readonly IBrowserContext _context; + + internal Cookies(IBrowserContext context) + { + _context = context; + } + + public void AddCookie(System.Net.Cookie cookie, Options options = null) + { + var expires = ((DateTimeOffset)cookie.Expires).ToUnixTimeMilliseconds(); + _context.AddCookiesAsync(new[] {PlaywrightCookie(cookie)}).Sync(); + } + + public async void DeleteAll() + { + _context.ClearCookiesAsync().Sync(); + } + + public void DeleteCookie(System.Net.Cookie cookie) + { + DeleteCookieNamed(cookie.Name); + } + + public void DeleteCookieNamed(string cookieName) + { + var filteredCookies = GetAll().Where((cookie) => cookie.Name != cookieName); + DeleteAll(); + _context.AddCookiesAsync(filteredCookies.Select(PlaywrightCookie)); + } + + private Cookie PlaywrightCookie(System.Net.Cookie cookie) + { + var expires = ((DateTimeOffset)cookie.Expires).ToUnixTimeMilliseconds(); + return new Cookie + { + Name = cookie.Name, + Value = cookie.Value, + Domain = cookie.Domain, + Path = cookie.Path, + Expires = expires < 1 ? -1 : expires, + HttpOnly = cookie.HttpOnly, + Secure = cookie.Secure + }; + } + + public IEnumerable GetAll() + { + var cookies = _context.CookiesAsync().Sync(); + return cookies.Select( + c => new System.Net.Cookie{ + Name = c.Name, + Value = c.Value, + Domain = c.Domain, + Path = c.Path, + Expires = c.Expires == -1 ? DateTime.MinValue : DateTimeOffset.FromUnixTimeMilliseconds(Convert.ToInt64(c.Expires)).DateTime, + HttpOnly = c.HttpOnly, + Secure = c.Secure + } + ); + } + + public System.Net.Cookie GetCookieNamed(string cookieName) + { + return GetAll().Where((cookie) => cookie.Name == cookieName).FirstOrDefault(); + } + + public void WaitUntilCookieExists(System.Net.Cookie cookie, + Options options) + { + var stopWatch = Stopwatch.StartNew(); + try + { + while (stopWatch.ElapsedMilliseconds < options.Timeout.TotalMilliseconds) + { + var allCookies = GetAll(); + if (allCookies.Any(x => x.Name.Trim() == cookie.Name)) + { + Console.WriteLine($"\t-> Cookie name '{cookie.Name}' exists."); + break; + } + + Thread.Sleep(options.RetryInterval); + } + + stopWatch.Stop(); + } + catch (Exception e) + { + Console.WriteLine($"\t-> Cookie name '{cookie.Name}' does NOT exist. After {stopWatch.Elapsed.TotalSeconds} seconds. {e.Message}"); + } + } + } +} diff --git a/src/Coypu/Drivers/Playwright/Dialogs.cs b/src/Coypu/Drivers/Playwright/Dialogs.cs new file mode 100644 index 00000000..3a0e0870 --- /dev/null +++ b/src/Coypu/Drivers/Playwright/Dialogs.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.Playwright; + +namespace Coypu.Drivers.Playwright +{ + internal class Dialogs + { + internal void ActOnDialog(string text, IPage page, Action trigger, string dialogType, Action dialogAction) + { + var dialogFound = false; + var match = false; + EventHandler listener = (_, dialog) => + { + dialogFound = true; + if (dialog.Type == dialogType && + (text == null || dialog.Message == text)) + { + match = true; + dialogAction(dialog); + } + else + { + dialog.DismissAsync(); + } + }; + page.Dialog += listener; + try + { + trigger.Invoke(); + } + finally + { + page.Dialog -= listener; // Only applies for while the trigger action is running + } + if (!dialogFound) + { + throw new MissingDialogException("No dialog was present to accept"); + } + if(!match) + { + throw new MissingDialogException("A dialog was present but didn't match the expected text or type."); + } + } + } +} diff --git a/src/Coypu/Drivers/Playwright/FrameFinder.cs b/src/Coypu/Drivers/Playwright/FrameFinder.cs new file mode 100644 index 00000000..38a406d1 --- /dev/null +++ b/src/Coypu/Drivers/Playwright/FrameFinder.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Playwright; + +namespace Coypu.Drivers.Playwright +{ + internal class FrameFinder + { + private readonly IPage _page; + private readonly XPath _xPath; + + public FrameFinder(IPage page) + { + _page = page; + _xPath = new XPath(); + } + + public IEnumerable FindFrame(string locator, + IEnumerable frameElements, + Options options) + { + return PlaywrightFrames(locator, frameElements, options); + } + + private IEnumerable PlaywrightFrames(string locator, + IEnumerable frameElements, + Options options) + { + var allPlaywrightFrames = frameElements.Select(f => new PlaywrightFrame(f)); + return allPlaywrightFrames.Where(f => { + var t = f.Title; + return f.Id == locator || + f.Name == locator || + (options.TextPrecisionExact + ? f.Title == locator + : f.Title + .Contains(locator)) || + FrameContentsMatch(f, locator, options); + }); + } + + private bool FrameContentsMatch(PlaywrightFrame frame, + string locator, + Options options) + { + return frame.Title == locator || + + ((IFrame) frame.Native).QuerySelectorAllAsync( + $"xpath=.//h1[{_xPath.IsText(locator, options)}]" + ).Sync().Any(); + } + } +} diff --git a/src/Coypu/Drivers/Playwright/PlaywrightDriver.cs b/src/Coypu/Drivers/Playwright/PlaywrightDriver.cs new file mode 100644 index 00000000..4ed99a11 --- /dev/null +++ b/src/Coypu/Drivers/Playwright/PlaywrightDriver.cs @@ -0,0 +1,407 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text.RegularExpressions; +using Cookie = System.Net.Cookie; +using Microsoft.Playwright; + +#pragma warning disable 1591 + +namespace Coypu.Drivers.Playwright +{ + public class PlaywrightDriver : IDriver + { + private readonly Browser _browser; + private readonly bool _headless; + private readonly Dialogs _dialogs; + private readonly IPlaywright _playwright; + private readonly IBrowser _playwrightBrowser; + private readonly IBrowserContext _context; + + public PlaywrightDriver(Browser browser, bool headless) + { + _dialogs = new Dialogs(); + _playwright = Microsoft.Playwright.Playwright.CreateAsync().Sync(); + _browser = browser; + _headless = headless; + var browserType = PlaywrightBrowserType(browser, _playwright); // TODO: map browser to playwright browser type + + _playwrightBrowser = browserType.LaunchAsync( + new BrowserTypeLaunchOptions + { + Headless = headless, + Channel = PlaywrightBrowserChannel(browser) + } + ).Sync(); + var page = _playwrightBrowser.NewPageAsync().Sync(); + _context = page.Context; + + Cookies = new Cookies(_context); + _context.SetDefaultTimeout(10000); + } + + private string PlaywrightBrowserChannel(Browser browser) + { + if (browser == Browser.Chrome) + return "chrome"; + if (browser == Browser.Edge) + return "msedge"; + + return null; + } + + private IBrowserType PlaywrightBrowserType(Browser browser, IPlaywright playwright) + { + if (browser == Browser.Chrome) + return playwright.Chromium; + if (browser == Browser.Edge) + return playwright.Chromium; + if (browser == Browser.Chromium) + return playwright.Chromium; + if (browser == Browser.Firefox) + return playwright.Firefox; + if (browser == Browser.Webkit) + return playwright.Webkit; + + throw new NotSupportedException($"Browser {browser} is not supported by Playwright"); + } + + protected bool NoJavascript => !_browser.Javascript; + + public bool Disposed { get; private set; } + + public Uri Location(Scope scope) + { + return new Uri(PlaywrightPage(scope).Url); + } + + public string Title(Scope scope) + { + return PlaywrightPage(scope).TitleAsync().Sync(); + } + + public Coypu.Cookies Cookies { get; set; } + public object Native => _context; + + public Element Window => new PlaywrightWindow(_context.Pages.First()); + + public IEnumerable FindFrames(string locator, + Scope scope, + Options options) + { + IPage page = Page(scope); + return new FrameFinder(page).FindFrame( + locator, + page.QuerySelectorAllAsync("iframe,frame").Sync(), + options + ); + } + + private static IPage Page(Scope scope) + { + var nativeScope = scope.Now().Native; + var page = nativeScope as IPage ?? + ((IElementHandle)nativeScope).OwnerFrameAsync().Sync().Page; + return page; + } + + public IEnumerable FindAllCss(string cssSelector, + Scope scope, + Options options, + Regex textPattern = null) + { + try { + var results = Element(scope).QuerySelectorAllAsync($"css={cssSelector}").Sync() + .Where(ValidateTextPattern(options, textPattern)) + .Where(e => IsDisplayed(e, options)) + .Select(BuildElement); + return results; + } + catch (AggregateException e) + { + throw new StaleElementException(e); + } + } + + private Func ValidateTextPattern(Options options, Regex textPattern) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + Func textMatches = e => + { + if (textPattern == null) return true; + + var text = e.InnerTextAsync().Sync(); + return text != null && textPattern.IsMatch(text.Trim()); + }; + + if (textPattern != null && options.ConsiderInvisibleElements) + throw new NotSupportedException("Cannot inspect the text of invisible elements."); + return textMatches; + } + + private bool IsDisplayed(IElementHandle e, + Options options) + { + return options.ConsiderInvisibleElements || e.IsVisibleAsync().Sync(); + } + + public IEnumerable FindAllXPath(string xpath, + Scope scope, + Options options) + { + try { + return Element(scope).QuerySelectorAllAsync($"xpath={xpath}").Sync() + .Where(e => IsDisplayed(e, options)) + .Select(BuildElement); + } + catch (AggregateException e) + { + throw new StaleElementException(e); + } + } + + private Element BuildElement(IElementHandle element) + { + var tagName = element.EvaluateAsync("e => e.tagName").Sync()?.GetString(); + + Element coypuElement = new[] {"iframe", "frame"}.Contains(tagName.ToLower()) + ? (Element) new PlaywrightFrame(element) : new PlaywrightElement(element); + + return coypuElement; + } + + public void AcceptAlert(string text, DriverScope scope, Action trigger) + { + _dialogs.ActOnDialog(text, Page(scope), trigger, DialogType.Alert, dialog => dialog.AcceptAsync()); + } + + public void AcceptConfirm(string text, DriverScope scope, Action trigger) + { + _dialogs.ActOnDialog(text, Page(scope), trigger, DialogType.Confirm, dialog => dialog.AcceptAsync()); + } + + public void CancelConfirm(string text, DriverScope scope, Action trigger) + { + _dialogs.ActOnDialog(text, Page(scope), trigger, DialogType.Confirm, dialog => dialog.DismissAsync()); + } + + public void AcceptPrompt(string text, string promptTalue, DriverScope scope, Action trigger) + { + _dialogs.ActOnDialog(text, Page(scope), trigger, DialogType.Prompt, dialog => dialog.AcceptAsync(promptTalue)); + } + + public void CancelPrompt(string text, DriverScope scope, Action trigger) + { + _dialogs.ActOnDialog(text, Page(scope), trigger, DialogType.Prompt, dialog => dialog.DismissAsync()); + } + + public void Visit(string url, + Scope scope) + { + IResponse response = PlaywrightPage(scope).GotoAsync(url).Sync(); + if (response != null && response.Status != 200) + { + throw new Exception("Failed to load page"); + } + } + + public void ClearBrowserCookies() + { + Cookies.DeleteAll(); + } + + public void Click(Element element) + { + PlaywrightElement(element).ClickAsync().Sync(); + } + + public void Hover(Element element) + { + PlaywrightElement(element).HoverAsync().Sync(); + } + + public void SendKeys(Element element, + string keys) + { + var playwrightElement = PlaywrightElement(element); + playwrightElement.FocusAsync().Sync(); + playwrightElement.EvaluateAsync("e => e.setSelectionRange(-1, -1)").Sync(); + keys.ToList().ForEach(key => playwrightElement.PressAsync(key.ToString()).Sync()); + } + + public void MaximiseWindow(Scope scope) + { + throw new NotSupportedException("MaximiseWindow is not currently supported by Playwright. https://github.com/microsoft/playwright/issues/4046"); + } + + public void Refresh(Scope scope) + { + ((IPage )scope.Now().Native).ReloadAsync().Sync(); + } + + public void ResizeTo(Size size, + Scope scope) + { + if (_playwrightBrowser.BrowserType == _playwright.Chromium && !_headless) { + size = new Size(size.Width - 2, size.Height - 80); + } + PlaywrightPage(scope).SetViewportSizeAsync(size.Width, size.Height).Sync(); + } + + public void SaveScreenshot(string fileName, + Scope scope) + { + PlaywrightPage(scope).ScreenshotAsync(new PageScreenshotOptions + { + Path = fileName + }).Sync(); + } + + public void GoBack(Scope scope) + { + PlaywrightPage(scope).GoBackAsync().Sync(); + } + + public void GoForward(Scope scope) + { + PlaywrightPage(scope).GoForwardAsync().Sync(); + } + + public IEnumerable GetBrowserCookies() + { + return Cookies.GetAll(); + } + + public IEnumerable FindWindows(string titleOrName, + Scope scope, + Options options) + { + try + { + return _context.Pages + .Select(p => new PlaywrightWindow(p)) + .Where(window => { + return + options.TextPrecisionExact && ( + window.Title == titleOrName + ) || + !options.TextPrecisionExact && ( + window.Title.Contains(titleOrName) + ); + }); + } + catch (PlaywrightException ex) + { + throw new MissingWindowException("The active window was closed.", ex); + } + + } + + public void Set(Element element, + string value) + { + var input = PlaywrightElement(element); + if (element["type"] == "file") + input.SetInputFilesAsync(value).Sync(); + else + input.FillAsync(value).Sync(); + } + + public void AcceptModalDialog(Scope scope) + { + throw new NotImplementedException("AcceptModalDialog is not supported by Playwright. Please wrap your action that will trigger the modal dialog within `Browser.AcceptAlert/AcceptConfirm/AcceptPrompt(...)`"); + } + + public void CancelModalDialog(Scope scope) + { + throw new NotImplementedException("AcceptModalDialog is not supported by Playwright. Please wrap your action that will trigger the modal dialog within `Browser.CancelConfirm/CancelPrompt(...)`"); + } + + public bool HasDialog(string withText, + Scope scope) + { + throw new NotImplementedException("HasModalDialog is not supported by Playwright. Please wrap your action that will trigger the modal dialog within `Browser.[Accepts/Cancels][Alert/Confirm/Prompt](..."); + } + + public void Check(Element field) + { + if (!field.Selected) + PlaywrightElement(field).CheckAsync().Sync(); + } + + public void Uncheck(Element field) + { + if (field.Selected) + PlaywrightElement(field).UncheckAsync().Sync(); + } + + public void Choose(Element field) + { + PlaywrightElement(field).CheckAsync().Sync(); + } + + public void SelectOption(Element select, Element option, string optionToSelect) + { + PlaywrightElement(select).SelectOptionAsync(optionToSelect).Sync(); + } + + public object ExecuteScript(string javascript, + Scope scope, + params object[] args) + { + var func = $"(arguments) => {Regex.Replace(javascript, "^return ", string.Empty)}"; + return + PlaywrightPage(scope) + .EvaluateAsync(func, ConvertScriptArgs(args)).Sync() + .ToString(); + } + + // TODO: extract duplication between Drivers + private static object[] ConvertScriptArgs(object[] args) + { + for (var i = 0; i < args.Length; ++i) + if (args[i] is Element argAsElement) + args[i] = argAsElement.Native; + + return args; + } + + private IElementHandle PlaywrightElement(Element element) + { + return (IElementHandle) element.Native; + } + + private IPage PlaywrightPage(Scope scope) + { + return (IPage) scope.Now().Native; + } + + private IElementHandle Element(Scope scope) + { + var scopeElement = scope.Now(); + var frame = scopeElement.Native as IFrame; + if (frame != null) { + return frame.QuerySelectorAsync("html").Sync(); + } + var page = scopeElement.Native as IPage; + if (page != null) { + return page.QuerySelectorAsync("html").Sync(); + } + return (IElementHandle) scopeElement.Native; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (Disposed) return; + + _playwright.Dispose(); + Disposed = true; + } + } +} diff --git a/src/Coypu/Drivers/Playwright/PlaywrightElement.cs b/src/Coypu/Drivers/Playwright/PlaywrightElement.cs new file mode 100644 index 00000000..23a875aa --- /dev/null +++ b/src/Coypu/Drivers/Playwright/PlaywrightElement.cs @@ -0,0 +1,67 @@ +using System.Linq; +using System.Runtime.Serialization; +using System.Threading.Tasks; +using Microsoft.Playwright; + +namespace Coypu.Drivers.Playwright +{ + internal class PlaywrightElement : Element + { + // ReSharper disable once InconsistentNaming + protected readonly IElementHandle _native; + + public PlaywrightElement(IElementHandle PlaywrightElement) + { + _native = PlaywrightElement; + } + + private string GetAttribute(string attributeName) + { + return _native.GetAttributeAsync(attributeName).Sync(); + } + + public string Id => GetAttribute("id"); + + public virtual string Text => _native.InnerTextAsync().Sync(); + + public string Value { + get { + var inputTags = new[] { "input", "textarea", "select" }; + if (inputTags.Contains(TagName.ToLower())) + return _native.InputValueAsync().Sync(); + + return this["value"]; + } + } + + public string Name => GetAttribute("name"); + + public string TagName => _native.EvaluateAsync("e => e.tagName").Sync()?.GetString(); + + public virtual string OuterHTML => _native.EvaluateAsync("el => el.outerHTML").Sync().ToString(); + + public virtual string InnerHTML => _native.InnerHTMLAsync().Sync(); + + public string Title => GetAttribute("title"); + + public bool Disabled => !_native.IsEnabledAsync().Sync(); + + public string SelectedOption + { + get + { + return _native.EvaluateAsync("sel => sel.options[sel.options.selectedIndex].innerText").Sync().ToString(); + } + } + + public bool Selected { + get { + return _native.IsCheckedAsync().Sync(); + } + } + + public virtual object Native => _native; + + public string this[string attributeName] => GetAttribute(attributeName); + } +} diff --git a/src/Coypu/Drivers/Playwright/PlaywrightFrame.cs b/src/Coypu/Drivers/Playwright/PlaywrightFrame.cs new file mode 100644 index 00000000..055f80f4 --- /dev/null +++ b/src/Coypu/Drivers/Playwright/PlaywrightFrame.cs @@ -0,0 +1,62 @@ +using Microsoft.Playwright; + +namespace Coypu.Drivers.Playwright +{ + internal class PlaywrightFrame : Element + { + + private readonly IFrame _frame; + private readonly IElementHandle _frameElement; + private string _id; + + public PlaywrightFrame(IElementHandle frameElement) + { + _frameElement = frameElement; + _frame = frameElement.ContentFrameAsync().Sync(); + } + + public string this[string attributeName] => GetAttribute(attributeName); + + private string GetAttribute(string attributeName) + { + return _frameElement.GetAttributeAsync(attributeName).Sync(); + } + + public string Text => FindBody().Text; + + public string OuterHTML => FindBody().OuterHTML; + + public string InnerHTML => FindBody().InnerHTML; + + public object Native + { + get + { + return _frame; + } + } + + public string Id { + get { + return this["id"]; + } + } + + public string Value => throw new System.NotImplementedException(); + + public string Name => _frame.Name; + + public string SelectedOption => throw new System.NotImplementedException(); + + public bool Selected => throw new System.NotImplementedException(); + + public string Title => _frame.TitleAsync().Sync(); + + public bool Disabled => throw new System.NotImplementedException(); + + private PlaywrightElement FindBody() + { + return new PlaywrightElement(_frame.QuerySelectorAsync("body").Sync()); + } + } +} diff --git a/src/Coypu/Drivers/Playwright/PlaywrightWindow.cs b/src/Coypu/Drivers/Playwright/PlaywrightWindow.cs new file mode 100644 index 00000000..1e3e890a --- /dev/null +++ b/src/Coypu/Drivers/Playwright/PlaywrightWindow.cs @@ -0,0 +1,47 @@ +using System; +using OpenQA.Selenium; +using Microsoft.Playwright; +using System.Linq; + +namespace Coypu.Drivers.Playwright +{ + internal class PlaywrightWindow : Element + { + private readonly IPage _page; + + public PlaywrightWindow(IPage page) + { + _page = page; + } + + public string Id => throw new NotSupportedException(); + + public string Text => ((IPage) Native).InnerTextAsync("xpath=/html/body").Sync(); + + public string InnerHTML => ((IPage) Native).InnerHTMLAsync("xpath=./*").Sync().ToString(); + + public string Title => _page.TitleAsync().Sync(); + + public bool Disabled => throw new NotSupportedException(); + + public string OuterHTML => ((IPage) Native).EvalOnSelectorAsync("html", "h => h.outerHTML").Sync().ToString(); + + public string Value => throw new NotSupportedException(); + + public string Name => throw new NotSupportedException(); + + public string SelectedOption => throw new NotSupportedException(); + + public bool Selected => throw new NotSupportedException(); + + public object Native + { + get + { + return _page; + } + } + + public string this[string attributeName] => throw new NotSupportedException(); + } +} diff --git a/src/Coypu/Drivers/Selenium/Cookies.cs b/src/Coypu/Drivers/Selenium/Cookies.cs new file mode 100644 index 00000000..17a02e96 --- /dev/null +++ b/src/Coypu/Drivers/Selenium/Cookies.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using OpenQA.Selenium; +using Cookie = System.Net.Cookie; + +namespace Coypu.Drivers.Selenium +{ + public class Cookies : Coypu.Cookies + { + private readonly IWebDriver _nativeDriver; + + public Cookies(IWebDriver nativeDriver) + { + _nativeDriver = nativeDriver; + } + + public void AddCookie(Cookie cookie, + Options options = null) + { + try + { + _nativeDriver.Manage() + .Cookies.AddCookie(new OpenQA.Selenium.Cookie( + cookie.Name, cookie.Value, cookie.Domain, cookie.Path, cookie.Expires + )); + _nativeDriver.Navigate() + .Refresh(); + WaitUntilCookieExists(cookie, options); + } + catch (Exception e) + { + Console.WriteLine($"\t-> Could not attach the cookie {cookie.Name} to the browser session. {e.Message}"); + } + } + + public void DeleteAll() + { + try + { + _nativeDriver.Manage() + .Cookies.DeleteAllCookies(); + _nativeDriver.Navigate() + .Refresh(); + } + catch (Exception e) + { + Console.WriteLine($"\t-> Could not delete the cookies from the browser session. {e.Message}"); + } + } + + public void DeleteCookie(Cookie cookie) + { + try + { + _nativeDriver.Manage() + .Cookies.DeleteCookie(new OpenQA.Selenium.Cookie( + cookie.Name, cookie.Value, cookie.Domain, cookie.Path, cookie.Expires + )); + _nativeDriver.Navigate() + .Refresh(); + } + catch (Exception e) + { + Console.WriteLine($"\t-> Could not delete the cookie name '{cookie.Name}' from the browser session. {e.Message}"); + } + } + + public void DeleteCookieNamed(string cookieName) + { + try + { + _nativeDriver.Manage() + .Cookies.DeleteCookieNamed(cookieName); + _nativeDriver.Navigate() + .Refresh(); + } + catch (Exception e) + { + Console.WriteLine($"\t-> Could not delete the cookie by name '{cookieName}' from the browser session. {e.Message}"); + } + } + + public IEnumerable GetAll() + { + return _nativeDriver.Manage() + .Cookies.AllCookies.Select(c => new Cookie { + Name = c.Name, + Value = c.Value, + Path = c.Path, + Domain = c.Domain, + Expires = DateTime.MaxValue + }); + } + + public Cookie GetCookieNamed(string cookieName) + { + var cookie = _nativeDriver.Manage() + .Cookies.GetCookieNamed(cookieName); + if (cookie == null) Console.WriteLine($"\t-> Could not get cookie by name '{cookieName}' from the browser session."); + return new Cookie( + cookie.Name, + cookie.Value, + cookie.Path, + cookie.Domain + ); + } + + public void WaitUntilCookieExists(Cookie cookie, + Options options) + { + var allCookies = _nativeDriver.Manage() + .Cookies.AllCookies; + var stopWatch = Stopwatch.StartNew(); + try + { + while (stopWatch.ElapsedMilliseconds < options.Timeout.TotalMilliseconds) + { + if (allCookies.Any(x => x.Name.Trim() == cookie.Name)) + { + Console.WriteLine($"\t-> Cookie name '{cookie.Name}' exists."); + break; + } + + allCookies = _nativeDriver.Manage() + .Cookies.AllCookies; + Thread.Sleep(options.RetryInterval); + } + + stopWatch.Stop(); + } + catch (Exception e) + { + Console.WriteLine($"\t-> Cookie name '{cookie.Name}' does NOT exist. After {stopWatch.Elapsed.TotalSeconds} seconds. {e.Message}"); + } + } + } +} diff --git a/src/Coypu/Drivers/Selenium/Dialogs.cs b/src/Coypu/Drivers/Selenium/Dialogs.cs index 4c463fe4..bd196eec 100644 --- a/src/Coypu/Drivers/Selenium/Dialogs.cs +++ b/src/Coypu/Drivers/Selenium/Dialogs.cs @@ -1,63 +1,68 @@ -using OpenQA.Selenium; - -namespace Coypu.Drivers.Selenium -{ - internal class Dialogs - { - private readonly IWebDriver _selenium; - - public Dialogs(IWebDriver selenium) - { - _selenium = selenium; - } - - public bool HasDialog(string withText) - { - return HasAnyDialog() && _selenium.SwitchTo() - .Alert() - .Text == withText; - } - - public bool HasAnyDialog() - { - try - { - return _selenium.SwitchTo() != null && - _selenium.SwitchTo() - .Alert() != null; - } - catch (NoAlertPresentException) - { - return false; - } - } - - public void AcceptModalDialog() - { - try - { - _selenium.SwitchTo() - .Alert() - .Accept(); - } - catch (NoAlertPresentException ex) - { - throw new MissingDialogException("No dialog was present to accept", ex); - } - } - - public void CancelModalDialog() - { - try - { - _selenium.SwitchTo() - .Alert() - .Dismiss(); - } - catch (NoAlertPresentException ex) - { - throw new MissingDialogException("No dialog was present to accept", ex); - } - } - } -} \ No newline at end of file +using OpenQA.Selenium; + +namespace Coypu.Drivers.Selenium +{ + internal class Dialogs + { + private readonly IWebDriver _selenium; + + public Dialogs(IWebDriver selenium) + { + _selenium = selenium; + } + + public bool HasDialog(string withText) + { + if (!HasAnyDialog() ) return false; + + var alert = _selenium.SwitchTo().Alert(); + + return alert.Text == withText; + } + + public bool HasAnyDialog() + { + try + { + return _selenium.SwitchTo() != null && + _selenium.SwitchTo() + .Alert() != null; + } + catch (NoAlertPresentException) + { + return false; + } + } + + public void AcceptModalDialog(string prompt = null) + { + try + { + var alert = _selenium.SwitchTo() + .Alert(); + if (prompt != null) { + alert.SendKeys(prompt); + } + alert.Accept(); + } + catch (NoAlertPresentException ex) + { + throw new MissingDialogException("No dialog was present to accept", ex); + } + } + + public void CancelModalDialog() + { + try + { + _selenium.SwitchTo() + .Alert() + .Dismiss(); + } + catch (NoAlertPresentException ex) + { + throw new MissingDialogException("No dialog was present to accept", ex); + } + } + } +} diff --git a/src/Coypu/Drivers/Selenium/DriverFactory.cs b/src/Coypu/Drivers/Selenium/DriverFactory.cs index 254ecabf..d778d5ae 100644 --- a/src/Coypu/Drivers/Selenium/DriverFactory.cs +++ b/src/Coypu/Drivers/Selenium/DriverFactory.cs @@ -1,37 +1,56 @@ -using System; -using OpenQA.Selenium; -using OpenQA.Selenium.Chrome; -using OpenQA.Selenium.Edge; -using OpenQA.Selenium.Firefox; -using OpenQA.Selenium.IE; -using OpenQA.Selenium.Opera; -using OpenQA.Selenium.Safari; - -namespace Coypu.Drivers.Selenium -{ - internal class DriverFactory - { - public IWebDriver NewWebDriver(Browser browser) - { - if (browser == Browser.Firefox) return new FirefoxDriver(); - if (browser == Browser.Chrome) return new ChromeDriver(); - if (browser == Browser.Edge) return new EdgeDriver(); - if (browser == Browser.Opera) return new OperaDriver(); - if (browser == Browser.Safari) return new SafariDriver(); - return browser == Browser.InternetExplorer - ? new InternetExplorerDriver(new InternetExplorerOptions - { - IntroduceInstabilityByIgnoringProtectedModeSettings = true, - EnableNativeEvents = true, - IgnoreZoomLevel = true - }) - : BrowserNotSupported(browser, null); - } - - private IWebDriver BrowserNotSupported(Browser browser, - Exception inner) - { - throw new BrowserNotSupportedException(browser, GetType(), inner); - } - } -} \ No newline at end of file +using System; +using OpenQA.Selenium; +using OpenQA.Selenium.Chrome; +using OpenQA.Selenium.DevTools.V85.HeadlessExperimental; +using OpenQA.Selenium.Edge; +using OpenQA.Selenium.Firefox; +using OpenQA.Selenium.IE; +using OpenQA.Selenium.Opera; +using OpenQA.Selenium.Safari; + +namespace Coypu.Drivers.Selenium +{ + internal class DriverFactory + { + public IWebDriver NewWebDriver(Browser browser, bool headless) + { + var firefoxOptions = new FirefoxOptions(); + var chromeOptions = new ChromeOptions(); + EdgeOptions edgeOptions = new EdgeOptions(); + if (headless) { + firefoxOptions.AddArgument("--headless"); + chromeOptions.AddArgument("--headless=new"); + edgeOptions.AddArgument("headless"); + edgeOptions.AddArgument("disable-gpu"); + if (browser == Browser.Safari) { + throw new NotSupportedException("Safari does not support headless mode"); + } + if (browser == Browser.Safari) { + throw new NotSupportedException("Opera does not support headless mode"); + } + if (browser == Browser.InternetExplorer) { + throw new NotSupportedException("Internet Explorer does not support headless mode"); + } + } + if (browser == Browser.Firefox) return new FirefoxDriver(firefoxOptions); + if (browser == Browser.Chrome) return new ChromeDriver(chromeOptions); + if (browser == Browser.Edge) return new EdgeDriver(edgeOptions); + if (browser == Browser.Opera) return new OperaDriver(); + if (browser == Browser.Safari) return new SafariDriver(); + return browser == Browser.InternetExplorer + ? new InternetExplorerDriver(new InternetExplorerOptions + { + IntroduceInstabilityByIgnoringProtectedModeSettings = true, + EnableNativeEvents = true, + IgnoreZoomLevel = true + }) + : BrowserNotSupported(browser, null); + } + + private IWebDriver BrowserNotSupported(Browser browser, + Exception inner) + { + throw new BrowserNotSupportedException(browser, GetType(), inner); + } + } +} diff --git a/src/Coypu/Drivers/Selenium/ElementFinder.cs b/src/Coypu/Drivers/Selenium/ElementFinder.cs index 16ad046a..f646e5d7 100644 --- a/src/Coypu/Drivers/Selenium/ElementFinder.cs +++ b/src/Coypu/Drivers/Selenium/ElementFinder.cs @@ -1,45 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using OpenQA.Selenium; - -namespace Coypu.Drivers.Selenium -{ - internal class ElementFinder - { - public IEnumerable FindAll(By by, - Scope scope, - Options options, - Func predicate = null) - { - try - { - return SeleniumScope(scope) - .FindElements(by) - .Where(e => Matches(predicate, e) && IsDisplayed(e, options)); - } - catch (StaleElementReferenceException e) - { - throw new StaleElementException(e); - } - } - - public ISearchContext SeleniumScope(Scope scope) - { - return (ISearchContext) scope.Now() - .Native; - } - - private static bool Matches(Func predicate, - IWebElement element) - { - return predicate == null || predicate(element); - } - - public bool IsDisplayed(IWebElement e, - Options options) - { - return options.ConsiderInvisibleElements || e.IsDisplayed(); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Linq; +using OpenQA.Selenium; + +namespace Coypu.Drivers.Selenium +{ + internal class ElementFinder + { + public IEnumerable FindAll(By by, + Scope scope, + Options options, + Func predicate = null) + { + try + { + return SeleniumScope(scope) + .FindElements(by) + .Where(e => Matches(predicate, e) && IsDisplayed(e, options)); + } + catch (StaleElementReferenceException e) + { + throw new StaleElementException(e); + } + } + + public ISearchContext SeleniumScope(Scope scope) + { + return (ISearchContext) scope.Now() + .Native; + } + + private static bool Matches(Func predicate, + IWebElement element) + { + return predicate == null || predicate(element); + } + + public bool IsDisplayed(IWebElement e, + Options options) + { + return options.ConsiderInvisibleElements || e.IsDisplayed(); + } + } +} diff --git a/src/Coypu/Drivers/Selenium/SeleniumFrame.cs b/src/Coypu/Drivers/Selenium/SeleniumFrame.cs index a10720c1..791b831c 100644 --- a/src/Coypu/Drivers/Selenium/SeleniumFrame.cs +++ b/src/Coypu/Drivers/Selenium/SeleniumFrame.cs @@ -1,40 +1,40 @@ -using OpenQA.Selenium; - -namespace Coypu.Drivers.Selenium -{ - internal class SeleniumFrame : SeleniumElement - { - private readonly SeleniumWindowManager _seleniumWindowManager; - - public SeleniumFrame(IWebElement seleniumElement, - IWebDriver selenium, - SeleniumWindowManager seleniumWindowManager) - : base(seleniumElement, selenium) - { - _seleniumWindowManager = seleniumWindowManager; - } - - public override string Text => FindBody() - .Text; - - public override string OuterHTML => FindBody() - .GetAttribute("outerHTML"); - - public override string InnerHTML => FindBody() - .GetAttribute("innerHTML"); - - public override object Native - { - get - { - _seleniumWindowManager.SwitchToFrame(_native); - return Selenium; - } - } - - private IWebElement FindBody() - { - return ((IWebDriver) Native).FindElement(By.CssSelector("body")); - } - } -} \ No newline at end of file +using OpenQA.Selenium; + +namespace Coypu.Drivers.Selenium +{ + internal class SeleniumFrame : SeleniumElement + { + private readonly SeleniumWindowManager _seleniumWindowManager; + + public SeleniumFrame(IWebElement seleniumElement, + IWebDriver selenium, + SeleniumWindowManager seleniumWindowManager) + : base(seleniumElement, selenium) + { + _seleniumWindowManager = seleniumWindowManager; + } + + public override string Text => FindBody() + .Text; + + public override string OuterHTML => FindBody() + .GetAttribute("outerHTML"); + + public override string InnerHTML => FindBody() + .GetAttribute("innerHTML"); + + public override object Native + { + get + { + _seleniumWindowManager.SwitchToFrame(_native); + return Selenium; + } + } + + private IWebElement FindBody() + { + return ((IWebDriver) Native).FindElement(By.CssSelector("body")); + } + } +} diff --git a/src/Coypu/Drivers/Selenium/SeleniumWebDriver.cs b/src/Coypu/Drivers/Selenium/SeleniumWebDriver.cs index 8cea69e0..ceff279e 100644 --- a/src/Coypu/Drivers/Selenium/SeleniumWebDriver.cs +++ b/src/Coypu/Drivers/Selenium/SeleniumWebDriver.cs @@ -1,326 +1,378 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text.RegularExpressions; -using OpenQA.Selenium; -using Cookie = System.Net.Cookie; - -#pragma warning disable 1591 - -namespace Coypu.Drivers.Selenium -{ - public class SeleniumWebDriver : IDriver - { - private readonly Browser _browser; - private readonly Dialogs _dialogs; - private readonly ElementFinder _elementFinder; - private readonly FrameFinder _frameFinder; - private readonly MouseControl _mouseControl; - private readonly SeleniumWindowManager _seleniumWindowManager; - private readonly TextMatcher _textMatcher; - private IWebDriver _webDriver; - private readonly WindowHandleFinder _windowHandleFinder; - - public SeleniumWebDriver(Browser browser) - : this(new DriverFactory().NewWebDriver(browser), browser) { } - - protected SeleniumWebDriver(IWebDriver webDriver, - Browser browser) - { - _webDriver = webDriver; - _browser = browser; - _elementFinder = new ElementFinder(); - _textMatcher = new TextMatcher(); - _dialogs = new Dialogs(_webDriver); - _mouseControl = new MouseControl(_webDriver); - _seleniumWindowManager = new SeleniumWindowManager(_webDriver); - _frameFinder = new FrameFinder(_webDriver, _elementFinder, new XPath(browser.UppercaseTagNames), _seleniumWindowManager); - _windowHandleFinder = new WindowHandleFinder(_webDriver, _seleniumWindowManager); - Cookies = new Cookies(_webDriver); - } - - private IJavaScriptExecutor JavaScriptExecutor => _webDriver as IJavaScriptExecutor; - - protected bool NoJavascript => !_browser.Javascript; - - public bool Disposed { get; private set; } - - public Uri Location(Scope scope) - { - _elementFinder.SeleniumScope(scope); - return new Uri(_webDriver.Url); - } - - public string Title(Scope scope) - { - _elementFinder.SeleniumScope(scope); - return _webDriver.Title; - } - - public Element Window => new SeleniumWindow(_webDriver, _webDriver.CurrentWindowHandle, _seleniumWindowManager); - - public Cookies Cookies { get; set; } - public object Native => _webDriver; - - public IEnumerable FindFrames(string locator, - Scope scope, - Options options) - { - return _frameFinder.FindFrame(locator, scope, options) - .Select(BuildElement); - } - - public IEnumerable FindAllCss(string cssSelector, - Scope scope, - Options options, - Regex textPattern = null) - { - return FindAll(By.CssSelector(cssSelector), scope, options, ValidateTextPattern(options, textPattern)) - .Select(BuildElement); - } - - public IEnumerable FindAllXPath(string xpath, - Scope scope, - Options options) - { - return FindAll(By.XPath(xpath), scope, options) - .Select(BuildElement); - } - - public bool HasDialog(string withText, - Scope scope) - { - _elementFinder.SeleniumScope(scope); - return _dialogs.HasDialog(withText); - } - - public void Visit(string url, - Scope scope) - { - _elementFinder.SeleniumScope(scope); - _webDriver.Navigate() - .GoToUrl(url); - } - - public void ClearBrowserCookies() - { - Cookies.DeleteAll(); - } - - public void Click(Element element) - { - SeleniumElement(element) - .Click(); - } - - public void Hover(Element element) - { - _mouseControl.Hover(element); - } - - public void SendKeys(Element element, - string keys) - { - SeleniumElement(element) - .SendKeys(keys); - } - - public void MaximiseWindow(Scope scope) - { - _elementFinder.SeleniumScope(scope); - _webDriver.Manage() - .Window.Maximize(); - } - - public void Refresh(Scope scope) - { - _elementFinder.SeleniumScope(scope); - _webDriver.Navigate() - .Refresh(); - } - - public void ResizeTo(Size size, - Scope scope) - { - _elementFinder.SeleniumScope(scope); - _webDriver.Manage() - .Window.Size = size; - } - - public void SaveScreenshot(string fileName, - Scope scope) - { - _elementFinder.SeleniumScope(scope); - var format = ImageFormatParser.GetImageFormat(fileName); - - var screenshot = ((ITakesScreenshot) _webDriver).GetScreenshot(); - screenshot.SaveAsFile(fileName, format); - } - - public void GoBack(Scope scope) - { - _elementFinder.SeleniumScope(scope); - _webDriver.Navigate() - .Back(); - } - - public void GoForward(Scope scope) - { - _elementFinder.SeleniumScope(scope); - _webDriver.Navigate() - .Forward(); - } - - public IEnumerable GetBrowserCookies() - { - return _webDriver.Manage() - .Cookies.AllCookies.Select(c => new Cookie(c.Name, c.Value, c.Path, c.Domain)); - } - - public IEnumerable FindWindows(string titleOrName, - Scope scope, - Options options) - { - _elementFinder.SeleniumScope(scope); - return _windowHandleFinder.FindWindowHandles(titleOrName, options) - .Select(h => new SeleniumWindow(_webDriver, h, _seleniumWindowManager)); - } - - public void Set(Element element, - string value) - { - try - { - SeleniumElement(element) - .Clear(); - } - catch (InvalidElementStateException) { } // Non user-editable elements (file inputs) - chrome/IE - catch (InvalidOperationException) { } // Non user-editable elements (file inputs) - firefox - - SendKeys(element, value); - } - - public void AcceptModalDialog(Scope scope) - { - _elementFinder.SeleniumScope(scope); - _dialogs.AcceptModalDialog(); - } - - public void CancelModalDialog(Scope scope) - { - _elementFinder.SeleniumScope(scope); - _dialogs.CancelModalDialog(); - } - - public void Check(Element field) - { - var seleniumElement = SeleniumElement(field); - - if (!seleniumElement.Selected) - seleniumElement.Click(); - } - - public void Uncheck(Element field) - { - var seleniumElement = SeleniumElement(field); - - if (seleniumElement.Selected) - seleniumElement.Click(); - } - - public void Choose(Element field) - { - SeleniumElement(field) - .Click(); - } - - public object ExecuteScript(string javascript, - Scope scope, - params object[] args) - { - if (NoJavascript) - throw new NotSupportedException("Javascript is not supported by " + _browser); - - _elementFinder.SeleniumScope(scope); - return JavaScriptExecutor.ExecuteScript(javascript, ConvertScriptArgs(args)); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (Disposed) return; - if (disposing) - { - AcceptAnyAlert(); - - _webDriver.Quit(); - _webDriver = null; - } - - Disposed = true; - } - - private Func ValidateTextPattern(Options options, - Regex textPattern) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - var textMatches = textPattern == null - ? (Func) null - : e => _textMatcher.TextMatches(e, textPattern); - - if (textPattern != null && options.ConsiderInvisibleElements) - throw new NotSupportedException("Cannot inspect the text of invisible elements."); - return textMatches; - } - - private IEnumerable FindAll(By by, - Scope scope, - Options options, - Func predicate = null) - { - return _elementFinder.FindAll(by, scope, options, predicate); - } - - private Element BuildElement(IWebElement element) - { - return new[] {"iframe", "frame"}.Contains(element.TagName.ToLower()) - ? new SeleniumFrame(element, _webDriver, _seleniumWindowManager) - : new SeleniumElement(element, _webDriver); - } - - private object[] ConvertScriptArgs(object[] args) - { - for (var i = 0; i < args.Length; ++i) - if (args[i] is Element argAsElement) - args[i] = argAsElement.Native; - - return args; - } - - private IWebElement SeleniumElement(Element element) - { - return (IWebElement) element.Native; - } - - private void AcceptAnyAlert() - { - try - { - _seleniumWindowManager.SwitchToWindow(_webDriver.WindowHandles[0]); - if (_dialogs.HasAnyDialog()) - _webDriver.SwitchTo() - .Alert() - .Accept(); - } - catch (WebDriverException) { } - catch (KeyNotFoundException) { } // Chrome - catch (InvalidOperationException) { } - catch (IndexOutOfRangeException) { } // No window handles - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text.RegularExpressions; +using OpenQA.Selenium; +using Cookie = System.Net.Cookie; + +#pragma warning disable 1591 + +namespace Coypu.Drivers.Selenium +{ + public class SeleniumWebDriver : IDriver + { + private readonly Browser _browser; + private readonly Dialogs _dialogs; + private readonly ElementFinder _elementFinder; + private readonly FrameFinder _frameFinder; + private readonly MouseControl _mouseControl; + private readonly SeleniumWindowManager _seleniumWindowManager; + private readonly TextMatcher _textMatcher; + private IWebDriver _webDriver; + private readonly WindowHandleFinder _windowHandleFinder; + + public SeleniumWebDriver(Browser browser, bool headless) + : this(new DriverFactory().NewWebDriver(browser, headless), browser) { } + + protected SeleniumWebDriver(IWebDriver webDriver, + Browser browser) + { + _webDriver = webDriver; + _browser = browser; + _elementFinder = new ElementFinder(); + _textMatcher = new TextMatcher(); + _dialogs = new Dialogs(_webDriver); + _mouseControl = new MouseControl(_webDriver); + _seleniumWindowManager = new SeleniumWindowManager(_webDriver); + _frameFinder = new FrameFinder(_webDriver, _elementFinder, new XPath(browser.UppercaseTagNames), _seleniumWindowManager); + _windowHandleFinder = new WindowHandleFinder(_webDriver, _seleniumWindowManager); + Cookies = new Cookies(_webDriver); + } + + private IJavaScriptExecutor JavaScriptExecutor => _webDriver as IJavaScriptExecutor; + + protected bool NoJavascript => !_browser.Javascript; + + public bool Disposed { get; private set; } + + public Uri Location(Scope scope) + { + _elementFinder.SeleniumScope(scope); + return new Uri(_webDriver.Url); + } + + public string Title(Scope scope) + { + _elementFinder.SeleniumScope(scope); + return _webDriver.Title; + } + + public Element Window => new SeleniumWindow(_webDriver, _webDriver.CurrentWindowHandle, _seleniumWindowManager); + + public Coypu.Cookies Cookies { get; set; } + public object Native => _webDriver; + + public IEnumerable FindFrames(string locator, + Scope scope, + Options options) + { + return _frameFinder.FindFrame(locator, scope, options) + .Select(BuildElement); + } + + public IEnumerable FindAllCss(string cssSelector, + Scope scope, + Options options, + Regex textPattern = null) + { + return FindAll(By.CssSelector(cssSelector), scope, options, ValidateTextPattern(options, textPattern)) + .Select(BuildElement); + } + + public IEnumerable FindAllXPath(string xpath, + Scope scope, + Options options) + { + return FindAll(By.XPath(xpath), scope, options) + .Select(BuildElement); + } + + public bool HasDialog(string withText, + Scope scope) + { + _elementFinder.SeleniumScope(scope); + return _dialogs.HasDialog(withText); + } + + public void Visit(string url, + Scope scope) + { + _elementFinder.SeleniumScope(scope); + _webDriver.Navigate() + .GoToUrl(url); + } + + public void ClearBrowserCookies() + { + Cookies.DeleteAll(); + } + + public void Click(Element element) + { + SeleniumElement(element) + .Click(); + } + + public void Hover(Element element) + { + _mouseControl.Hover(element); + } + + public void SendKeys(Element element, + string keys) + { + SeleniumElement(element) + .SendKeys(keys); + } + + public void MaximiseWindow(Scope scope) + { + _elementFinder.SeleniumScope(scope); + _webDriver.Manage() + .Window.Maximize(); + } + + public void Refresh(Scope scope) + { + _elementFinder.SeleniumScope(scope); + _webDriver.Navigate() + .Refresh(); + } + + public void ResizeTo(Size size, + Scope scope) + { + _elementFinder.SeleniumScope(scope); + _webDriver.Manage() + .Window.Size = size; + } + + public void SaveScreenshot(string fileName, + Scope scope) + { + _elementFinder.SeleniumScope(scope); + var format = ImageFormatParser.GetImageFormat(fileName); + + var screenshot = ((ITakesScreenshot) _webDriver).GetScreenshot(); + screenshot.SaveAsFile(fileName, format); + } + + public void GoBack(Scope scope) + { + _elementFinder.SeleniumScope(scope); + _webDriver.Navigate() + .Back(); + } + + public void GoForward(Scope scope) + { + _elementFinder.SeleniumScope(scope); + _webDriver.Navigate() + .Forward(); + } + + public IEnumerable GetBrowserCookies() + { + return _webDriver.Manage() + .Cookies.AllCookies.Select(c => new Cookie(c.Name, c.Value, c.Path, c.Domain)); + } + + public IEnumerable FindWindows(string titleOrName, + Scope scope, + Options options) + { + _elementFinder.SeleniumScope(scope); + return _windowHandleFinder.FindWindowHandles(titleOrName, options) + .Select(h => new SeleniumWindow(_webDriver, h, _seleniumWindowManager)); + } + + public void Set(Element element, + string value) + { + try + { + SeleniumElement(element) + .Clear(); + } + catch (InvalidElementStateException) { } // Non user-editable elements (file inputs) - chrome/IE + catch (InvalidOperationException) { } // Non user-editable elements (file inputs) - firefox + + SendKeys(element, value); + } + + public void AcceptModalDialog(Scope scope) + { + _elementFinder.SeleniumScope(scope); + _dialogs.AcceptModalDialog(); + } + + public void CancelModalDialog(Scope scope) + { + _elementFinder.SeleniumScope(scope); + _dialogs.CancelModalDialog(); + } + + public void Check(Element field) + { + var seleniumElement = SeleniumElement(field); + + if (!seleniumElement.Selected) + seleniumElement.Click(); + } + + public void Uncheck(Element field) + { + var seleniumElement = SeleniumElement(field); + + if (seleniumElement.Selected) + seleniumElement.Click(); + } + + public void Choose(Element field) + { + SeleniumElement(field) + .Click(); + } + + public void SelectOption(Element select, Element option, string optionToSelect) + { + Click(option); + } + public object ExecuteScript(string javascript, + Scope scope, + params object[] args) + { + if (NoJavascript) + throw new NotSupportedException("Javascript is not supported by " + _browser); + + _elementFinder.SeleniumScope(scope); + return JavaScriptExecutor.ExecuteScript(javascript, ConvertScriptArgs(args)); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (Disposed) return; + if (disposing) + { + AcceptAnyAlert(); + + _webDriver.Quit(); + _webDriver = null; + } + + Disposed = true; + } + + private Func ValidateTextPattern(Options options, + Regex textPattern) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + var textMatches = textPattern == null + ? (Func) null + : e => _textMatcher.TextMatches(e, textPattern); + + if (textPattern != null && options.ConsiderInvisibleElements) + throw new NotSupportedException("Cannot inspect the text of invisible elements."); + return textMatches; + } + + private IEnumerable FindAll(By by, + Scope scope, + Options options, + Func predicate = null) + { + return _elementFinder.FindAll(by, scope, options, predicate); + } + + private Element BuildElement(IWebElement element) + { + return new[] {"iframe", "frame"}.Contains(element.TagName.ToLower()) + ? new SeleniumFrame(element, _webDriver, _seleniumWindowManager) + : new SeleniumElement(element, _webDriver); + } + + private static object[] ConvertScriptArgs(object[] args) + { + for (var i = 0; i < args.Length; ++i) + if (args[i] is Element argAsElement) + args[i] = argAsElement.Native; + + return args; + } + + private IWebElement SeleniumElement(Element element) + { + return (IWebElement) element.Native; + } + + private void AcceptAnyAlert() + { + try + { + _seleniumWindowManager.SwitchToWindow(_webDriver.WindowHandles[0]); + if (_dialogs.HasAnyDialog()) + _webDriver.SwitchTo() + .Alert() + .Accept(); + } + catch (WebDriverException) { } + catch (KeyNotFoundException) { } // Chrome + catch (InvalidOperationException) { } + catch (IndexOutOfRangeException) { } // No window handles + } + + public void AcceptAlert(string text, DriverScope scope, Action trigger) + { + trigger.Invoke(); + _elementFinder.SeleniumScope(scope); + if (text != null && _dialogs.HasAnyDialog() && !_dialogs.HasDialog(text)) { + throw new MissingDialogException("A dialog was present but didn't match the expected text."); + } + _dialogs.AcceptModalDialog(); + } + + public void CancelAlert(string text, DriverScope scope, Action trigger) + { + trigger.Invoke(); + _elementFinder.SeleniumScope(scope); + if (text != null && _dialogs.HasAnyDialog() && !_dialogs.HasDialog(text)) { + throw new MissingDialogException("A dialog was present but didn't match the expected text."); + } + _dialogs.CancelModalDialog(); + } + + public void AcceptConfirm(string text, DriverScope scope, Action trigger) + { + // Webdriver cannot distinguish between confirm and alert + AcceptAlert(text, scope, trigger); + } + + public void CancelConfirm(string text, DriverScope scope, Action trigger) + { + // Webdriver cannot distinguish between confirm and alert + CancelAlert(text, scope, trigger); + } + + public void AcceptPrompt(string text, string promptValue, DriverScope scope, Action trigger) + { + trigger.Invoke(); + _elementFinder.SeleniumScope(scope); + if (text != null && _dialogs.HasAnyDialog() && !_dialogs.HasDialog(text)) { + throw new MissingDialogException("A dialog was present but didn't match the expected text."); + } + _dialogs.AcceptModalDialog(promptValue); + } + + public void CancelPrompt(string text, DriverScope scope, Action trigger) + { + // Webdriver cannot distinguish between prompt and alert + CancelAlert(text, scope, trigger); + } + } +} diff --git a/src/Coypu/Drivers/Selenium/SeleniumWindowManager.cs b/src/Coypu/Drivers/Selenium/SeleniumWindowManager.cs index 599ba370..6875bc75 100644 --- a/src/Coypu/Drivers/Selenium/SeleniumWindowManager.cs +++ b/src/Coypu/Drivers/Selenium/SeleniumWindowManager.cs @@ -1,56 +1,56 @@ -using OpenQA.Selenium; -using OpenQA.Selenium.Firefox; - -namespace Coypu.Drivers.Selenium -{ - internal class SeleniumWindowManager - { - private readonly IWebDriver _webDriver; - private IWebDriver _switchedToFrame; - private IWebElement _switchedToFrameElement; - - public SeleniumWindowManager(IWebDriver webDriver) - { - _webDriver = webDriver; - } - - public bool SwitchedToAFrame => _switchedToFrame != null; - - public string LastKnownWindowHandle { get; private set; } - - public IWebDriver SwitchToFrame(IWebElement webElement) - { - if (Equals(_switchedToFrameElement, webElement)) - return _switchedToFrame; - - var frame = _webDriver.SwitchTo() - .Frame(webElement); - - _switchedToFrameElement = webElement; - _switchedToFrame = frame; - - return frame; - } - - public void SwitchToWindow(string windowName) - { - if (LastKnownWindowHandle != windowName || SwitchedToAFrame) - { - _webDriver.SwitchTo() - .Window(windowName); - - // Fix for https://bugzilla.mozilla.org/show_bug.cgi?id=1305822 - if (_webDriver is FirefoxDriver) - { - _webDriver.SwitchTo() - .DefaultContent(); - } - - LastKnownWindowHandle = windowName; - } - - _switchedToFrame = null; - _switchedToFrameElement = null; - } - } -} \ No newline at end of file +using OpenQA.Selenium; +using OpenQA.Selenium.Firefox; + +namespace Coypu.Drivers.Selenium +{ + internal class SeleniumWindowManager + { + private readonly IWebDriver _webDriver; + private IWebDriver _switchedToFrame; + private IWebElement _switchedToFrameElement; + + public SeleniumWindowManager(IWebDriver webDriver) + { + _webDriver = webDriver; + } + + public bool SwitchedToAFrame => _switchedToFrame != null; + + public string LastKnownWindowHandle { get; private set; } + + public IWebDriver SwitchToFrame(IWebElement webElement) + { + if (Equals(_switchedToFrameElement, webElement)) + return _switchedToFrame; + + var frame = _webDriver.SwitchTo() + .Frame(webElement); + + _switchedToFrameElement = webElement; + _switchedToFrame = frame; + + return frame; + } + + public void SwitchToWindow(string windowName) + { + if (LastKnownWindowHandle != windowName || SwitchedToAFrame) + { + _webDriver.SwitchTo() + .Window(windowName); + + // Fix for https://bugzilla.mozilla.org/show_bug.cgi?id=1305822 + if (_webDriver is FirefoxDriver) + { + _webDriver.SwitchTo() + .DefaultContent(); + } + + LastKnownWindowHandle = windowName; + } + + _switchedToFrame = null; + _switchedToFrameElement = null; + } + } +} diff --git a/src/Coypu/Drivers/Selenium/WindowHandleFinder.cs b/src/Coypu/Drivers/Selenium/WindowHandleFinder.cs index f2977ffd..b1eeeb93 100644 --- a/src/Coypu/Drivers/Selenium/WindowHandleFinder.cs +++ b/src/Coypu/Drivers/Selenium/WindowHandleFinder.cs @@ -1,83 +1,83 @@ -using System; -using System.Collections.Generic; -using OpenQA.Selenium; - -namespace Coypu.Drivers.Selenium -{ - internal class WindowHandleFinder - { - private readonly SeleniumWindowManager _seleniumWindowManager; - private readonly IWebDriver _webDriver; - - public WindowHandleFinder(IWebDriver webDriver, - SeleniumWindowManager seleniumWindowManager) - { - _webDriver = webDriver; - _seleniumWindowManager = seleniumWindowManager; - } - - public IEnumerable FindWindowHandles(string titleOrName, - Options options) - { - var currentHandle = GetCurrentWindowHandle(); - IList matchingWindowHandles = new List(); - - try - { - _seleniumWindowManager.SwitchToWindow(titleOrName); - matchingWindowHandles.Add(_webDriver.CurrentWindowHandle); - } - catch (NoSuchWindowException) - { - foreach (var windowHandle in _webDriver.WindowHandles) - { - _seleniumWindowManager.SwitchToWindow(windowHandle); - if (options.TextPrecisionExact) - { - if (ExactMatch(titleOrName, windowHandle)) - matchingWindowHandles.Add(windowHandle); - } - else - { - if (SubstringMatch(titleOrName)) - matchingWindowHandles.Add(windowHandle); - } - } - } - - try - { - _seleniumWindowManager.SwitchToWindow(currentHandle); - } - catch (NoSuchWindowException ex) - { - throw new MissingWindowException("The active window was closed. Coypu should prevent this by ensuring fresh scope higher up.", ex); - } - - return matchingWindowHandles; - } - - private bool SubstringMatch(string titleOrName) - { - return _webDriver.Title.Contains(titleOrName); - } - - private bool ExactMatch(string titleOrName, - string windowHandle) - { - return windowHandle == titleOrName || _webDriver.Title == titleOrName; - } - - public string GetCurrentWindowHandle() - { - try - { - return _webDriver.CurrentWindowHandle; - } - catch (NoSuchWindowException) { } - catch (InvalidOperationException) { } - - return null; - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using OpenQA.Selenium; + +namespace Coypu.Drivers.Selenium +{ + internal class WindowHandleFinder + { + private readonly SeleniumWindowManager _seleniumWindowManager; + private readonly IWebDriver _webDriver; + + public WindowHandleFinder(IWebDriver webDriver, + SeleniumWindowManager seleniumWindowManager) + { + _webDriver = webDriver; + _seleniumWindowManager = seleniumWindowManager; + } + + public IEnumerable FindWindowHandles(string titleOrName, + Options options) + { + var currentHandle = GetCurrentWindowHandle(); + IList matchingWindowHandles = new List(); + + try + { + _seleniumWindowManager.SwitchToWindow(titleOrName); + matchingWindowHandles.Add(_webDriver.CurrentWindowHandle); + } + catch (NoSuchWindowException) + { + foreach (var windowHandle in _webDriver.WindowHandles) + { + _seleniumWindowManager.SwitchToWindow(windowHandle); + if (options.TextPrecisionExact) + { + if (ExactMatch(titleOrName, windowHandle)) + matchingWindowHandles.Add(windowHandle); + } + else + { + if (SubstringMatch(titleOrName)) + matchingWindowHandles.Add(windowHandle); + } + } + } + + try + { + _seleniumWindowManager.SwitchToWindow(currentHandle); + } + catch (NoSuchWindowException ex) + { + throw new MissingWindowException("The active window was closed.", ex); + } + + return matchingWindowHandles; + } + + private bool SubstringMatch(string titleOrName) + { + return _webDriver.Title.Contains(titleOrName); + } + + private bool ExactMatch(string titleOrName, + string windowHandle) + { + return windowHandle == titleOrName || _webDriver.Title == titleOrName; + } + + public string GetCurrentWindowHandle() + { + try + { + return _webDriver.CurrentWindowHandle; + } + catch (NoSuchWindowException) { } + catch (InvalidOperationException) { } + + return null; + } + } +} diff --git a/src/Coypu/Finders/DocumentElementFinder.cs b/src/Coypu/Finders/DocumentElementFinder.cs index a074819a..d1e80b05 100644 --- a/src/Coypu/Finders/DocumentElementFinder.cs +++ b/src/Coypu/Finders/DocumentElementFinder.cs @@ -1,22 +1,22 @@ -using System.Collections.Generic; - -namespace Coypu.Finders -{ - internal class DocumentElementFinder : ElementFinder - { - private Element window; - - public DocumentElementFinder(IDriver driver, Options options) : base(driver, "Window", null, options) - { - } - - public override bool SupportsSubstringTextMatching => false; - - internal override IEnumerable Find(Options options) - { - return new[] { window = (window ?? Driver.Window) }; - } - - internal override string QueryDescription => "Document Element"; - } -} \ No newline at end of file +using System.Collections.Generic; + +namespace Coypu.Finders +{ + internal class DocumentElementFinder : ElementFinder + { + private Element window; + + public DocumentElementFinder(IDriver driver, Options options) : base(driver, "Window", null, options) + { + } + + public override bool SupportsSubstringTextMatching => false; + + internal override IEnumerable Find(Options options) + { + return new[] { window ??= Driver.Window }; + } + + internal override string QueryDescription => "Document Element"; + } +} diff --git a/src/Coypu/Finders/FinderOptionsDisambiguationStrategy.cs b/src/Coypu/Finders/FinderOptionsDisambiguationStrategy.cs index 06b3a886..376d3a00 100644 --- a/src/Coypu/Finders/FinderOptionsDisambiguationStrategy.cs +++ b/src/Coypu/Finders/FinderOptionsDisambiguationStrategy.cs @@ -1,39 +1,39 @@ -using System.Linq; - -namespace Coypu.Finders -{ - internal class FinderOptionsDisambiguationStrategy : DisambiguationStrategy - { - public Element ResolveQuery(ElementFinder elementFinder) - { - Element[] results; - - if (elementFinder.Options.TextPrecision == TextPrecision.PreferExact) - results = PreferExact(elementFinder); - else - results = Find(elementFinder); - - if (elementFinder.Options.Match == Match.Single && results.Length > 1) - throw new AmbiguousException(elementFinder.Options.BuildAmbiguousMessage(elementFinder.QueryDescription, results.Length)); - - if (!results.Any()) - throw elementFinder.GetMissingException(); - - return results.First(); - } - - private static Element[] PreferExact(ElementFinder elementFinder) - { - var results = Find(elementFinder, Options.Exact); - if (results.Any() || !elementFinder.SupportsSubstringTextMatching) - return results; - - return Find(elementFinder, Options.Substring); - } - - private static Element[] Find(ElementFinder elementFinder, Options preferredOptions = null) - { - return elementFinder.Find((Options.Merge(preferredOptions, elementFinder.Options))).ToArray(); - } - } -} \ No newline at end of file +using System.Linq; + +namespace Coypu.Finders +{ + internal class FinderOptionsDisambiguationStrategy : DisambiguationStrategy + { + public Element ResolveQuery(ElementFinder elementFinder) + { + Element[] results; + + if (elementFinder.Options.TextPrecision == TextPrecision.PreferExact) + results = PreferExact(elementFinder); + else + results = Find(elementFinder); + + if (elementFinder.Options.Match == Match.Single && results.Length > 1) + throw new AmbiguousException(elementFinder.Options.BuildAmbiguousMessage(elementFinder.QueryDescription, results.Length)); + + if (!results.Any()) + throw elementFinder.GetMissingException(); + + return results.First(); + } + + private static Element[] PreferExact(ElementFinder elementFinder) + { + var results = Find(elementFinder, Options.Exact); + if (results.Any() || !elementFinder.SupportsSubstringTextMatching) + return results; + + return Find(elementFinder, Options.Substring); + } + + private static Element[] Find(ElementFinder elementFinder, Options preferredOptions = null) + { + return elementFinder.Find(Options.Merge(preferredOptions, elementFinder.Options)).ToArray(); + } + } +} diff --git a/src/Coypu/Finders/FrameFinder.cs b/src/Coypu/Finders/FrameFinder.cs index 231b14d4..ea3152fd 100644 --- a/src/Coypu/Finders/FrameFinder.cs +++ b/src/Coypu/Finders/FrameFinder.cs @@ -1,18 +1,18 @@ -using System.Collections.Generic; - -namespace Coypu.Finders -{ - internal class FrameFinder : ElementFinder - { - internal FrameFinder(IDriver driver, string locator, DriverScope scope, Options options) : base(driver, locator, scope, options) { } - - public override bool SupportsSubstringTextMatching => true; - - internal override IEnumerable Find(Options options) - { - return Driver.FindFrames(Locator, Scope, options); - } - - internal override string QueryDescription => "frame: " + Locator; - } -} \ No newline at end of file +using System.Collections.Generic; + +namespace Coypu.Finders +{ + internal class FrameFinder : ElementFinder + { + internal FrameFinder(IDriver driver, string locator, DriverScope scope, Options options) : base(driver, locator, scope, options) { } + + public override bool SupportsSubstringTextMatching => true; + + internal override IEnumerable Find(Options options) + { + return Driver.FindFrames(Locator, Scope, options); + } + + internal override string QueryDescription => "frame: " + Locator; + } +} diff --git a/src/Coypu/Finders/OptionFinder.cs b/src/Coypu/Finders/OptionFinder.cs index 2f9fd65e..416a9cec 100644 --- a/src/Coypu/Finders/OptionFinder.cs +++ b/src/Coypu/Finders/OptionFinder.cs @@ -1,19 +1,19 @@ -using System; -using Coypu.Drivers; - -namespace Coypu.Finders -{ - internal class OptionFinder : XPathQueryFinder - { - internal OptionFinder(IDriver driver, string locator, DriverScope scope, Options options) : base(driver, locator, scope, options) { } - - public override bool SupportsSubstringTextMatching => true; - - protected override Func GetQuery(Html html) - { - return html.Option; - } - - internal override string QueryDescription => "option: " + Locator; - } -} \ No newline at end of file +using System; +using Coypu.Drivers; + +namespace Coypu.Finders +{ + internal class OptionFinder : XPathQueryFinder + { + internal OptionFinder(IDriver driver, string locator, DriverScope scope, Options options) : base(driver, locator, scope, Options.Merge(Options.Invisible, options)) { } + + public override bool SupportsSubstringTextMatching => true; + + protected override Func GetQuery(Html html) + { + return html.Option; + } + + internal override string QueryDescription => "option: " + Locator; + } +} diff --git a/src/Coypu/Finders/XPathFinder.cs b/src/Coypu/Finders/XPathFinder.cs index 95dbe33d..1317413c 100644 --- a/src/Coypu/Finders/XPathFinder.cs +++ b/src/Coypu/Finders/XPathFinder.cs @@ -1,63 +1,63 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; -using Coypu.Drivers; - -namespace Coypu.Finders -{ - internal class XPathFinder : XPathQueryFinder - { - private readonly string text; - private readonly Regex textPattern; - - protected string SelectorType => "xpath"; - - public XPathFinder(IDriver driver, string locator, DriverScope scope, Options options) - : base(driver, locator, scope, options) - { - } - - public XPathFinder(IDriver driver, string locator, DriverScope scope, Options options, Regex textPattern) - : base(driver, locator, scope, options) - { - this.textPattern = textPattern; - } - - public XPathFinder(IDriver driver, string locator, DriverScope scope, Options options, string text) - : base(driver, locator, scope, options) - { - this.text = text; - } - - public override bool SupportsSubstringTextMatching => true; - - internal override string QueryDescription - { - get - { - var queryDesciption = SelectorType + ": " + Locator; - if (text != null) - queryDesciption += " with text " + text; - if (textPattern != null) - queryDesciption += " with text matching /" + (text ?? textPattern.ToString()) + "/"; - - return queryDesciption; - } - } - - protected override Func GetQuery(Html html) - { - return ((locator, options) => - { - if (string.IsNullOrEmpty(text)) - { - return Locator; - } - else - { - return Locator + XPath.Where(html.IsText(text, options)); - } - }); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Coypu.Drivers; + +namespace Coypu.Finders +{ + internal class XPathFinder : XPathQueryFinder + { + private readonly string text; + private readonly Regex textPattern; + + protected string SelectorType => "xpath"; + + public XPathFinder(IDriver driver, string locator, DriverScope scope, Options options) + : base(driver, locator, scope, options) + { + } + + public XPathFinder(IDriver driver, string locator, DriverScope scope, Options options, Regex textPattern) + : base(driver, locator, scope, options) + { + this.textPattern = textPattern; + } + + public XPathFinder(IDriver driver, string locator, DriverScope scope, Options options, string text) + : base(driver, locator, scope, options) + { + this.text = text; + } + + public override bool SupportsSubstringTextMatching => true; + + internal override string QueryDescription + { + get + { + var queryDesciption = SelectorType + ": " + Locator; + if (text != null) + queryDesciption += " with text " + text; + if (textPattern != null) + queryDesciption += " with text matching /" + (text ?? textPattern.ToString()) + "/"; + + return queryDesciption; + } + } + + protected override Func GetQuery(Html html) + { + return ((locator, options) => + { + if (string.IsNullOrEmpty(text)) + { + return Locator; + } + else + { + return Locator + XPath.Where(html.IsText(text, options)); + } + }); + } + } +} diff --git a/src/Coypu/IDriver.cs b/src/Coypu/IDriver.cs index 12bfbdde..bddcebea 100644 --- a/src/Coypu/IDriver.cs +++ b/src/Coypu/IDriver.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Drawing; using System.Text.RegularExpressions; -using OpenQA.Selenium; using Cookie = System.Net.Cookie; #pragma warning disable 1591 @@ -28,11 +27,18 @@ public interface IDriver : IDisposable [Obsolete("Please use instead: _browserSession.Driver.Cookies.DeleteAll()")] void ClearBrowserCookies(); - void Click(Element element); + [Obsolete("Please use instead: AcceptAlert/AcceptConfirm/AcceptPrompt")] void AcceptModalDialog(Scope scope); + [Obsolete("Please use instead: CancelAlert/CancelConfirm/CancelPrompt")] void CancelModalDialog(Scope scope); + [Obsolete("Please use instead: [Accepts/Cancels][Alert/Confirm/Prompt]")] bool HasDialog(string withText, Scope scope); + void AcceptAlert(string text, DriverScope root, Action trigger); + void AcceptConfirm(string text, DriverScope root, Action trigger); + void CancelConfirm(string text, DriverScope root, Action trigger); + void AcceptPrompt(string text, string value, DriverScope root, Action trigger); + void CancelPrompt(string text, DriverScope root, Action trigger); void Choose(Element field); void Check(Element field); void Uncheck(Element field); @@ -46,5 +52,6 @@ public interface IDriver : IDisposable void ResizeTo(Size size, Scope Scope); void SaveScreenshot(string fileName, Scope scope); void SendKeys(Element element, string keys); - } -} \ No newline at end of file + void SelectOption(Element select, Element option, string optionToSelect); + } +} diff --git a/src/Coypu/Queries/ElementMissingQuery.cs b/src/Coypu/Queries/ElementMissingQuery.cs index b9007914..217eeb72 100644 --- a/src/Coypu/Queries/ElementMissingQuery.cs +++ b/src/Coypu/Queries/ElementMissingQuery.cs @@ -1,34 +1,34 @@ -namespace Coypu.Queries -{ - internal class ElementMissingQuery : DriverScopeQuery - { - protected internal ElementMissingQuery(DriverScope driverScope, Options options) - : base(driverScope, options) - { - } - - public override object ExpectedResult => true; - - public override bool Run() - { - try - { - Scope.Stale = true; - Scope.FindElement(); - return false; - } - catch (MissingHtmlException) - { - return true; - } - catch (MissingWindowException) - { - return true; - } - catch (StaleElementException) - { - return true; - } - } - } -} \ No newline at end of file +namespace Coypu.Queries +{ + internal class ElementMissingQuery : DriverScopeQuery + { + protected internal ElementMissingQuery(DriverScope driverScope, Options options) + : base(driverScope, options) + { + } + + public override object ExpectedResult => true; + + public override bool Run() + { + try + { + Scope.Stale = true; + Scope.FindElement(); + return false; + } + catch (MissingHtmlException) + { + return true; + } + catch (MissingWindowException) + { + return true; + } + catch (StaleElementException) + { + return true; + } + } + } +} diff --git a/src/Coypu/Queries/HasDialogQuery.cs b/src/Coypu/Queries/HasDialogQuery.cs index c21f7392..ec7e9847 100644 --- a/src/Coypu/Queries/HasDialogQuery.cs +++ b/src/Coypu/Queries/HasDialogQuery.cs @@ -1,20 +1,20 @@ -namespace Coypu.Queries -{ - internal class HasDialogQuery : DriverScopeQuery - { - private readonly IDriver driver; - private readonly string text; - public override object ExpectedResult => true; - - protected internal HasDialogQuery(IDriver driver, string text, DriverScope driverScope, Options options) : base(driverScope,options) - { - this.driver = driver; - this.text = text; - } - - public override bool Run() - { - return driver.HasDialog(text,Scope); - } - } -} \ No newline at end of file +namespace Coypu.Queries +{ + internal class HasDialogQuery : DriverScopeQuery + { + private readonly IDriver driver; + private readonly string text; + public override object ExpectedResult => true; + + protected internal HasDialogQuery(IDriver driver, string text, DriverScope driverScope, Options options) : base(driverScope,options) + { + this.driver = driver; + this.text = text; + } + + public override bool Run() + { + return driver.HasDialog(text, Scope); + } + } +} diff --git a/src/Coypu/SessionConfiguration.cs b/src/Coypu/SessionConfiguration.cs index e3603efd..58cd307c 100644 --- a/src/Coypu/SessionConfiguration.cs +++ b/src/Coypu/SessionConfiguration.cs @@ -1,75 +1,84 @@ -using System; -using Coypu.Drivers.Selenium; - -namespace Coypu -{ - /// - /// Global configuration settings - /// - public class SessionConfiguration : Options - { - const string DEFAULT_APP_HOST = "localhost"; - const int DEFAULT_PORT = 80; - - private string appHost; - - /// - /// New default configuration - /// - public SessionConfiguration() - { - AppHost = DEFAULT_APP_HOST; - Port = DEFAULT_PORT; - SSL = false; - Browser = Drivers.Browser.Firefox; - Driver = typeof (SeleniumWebDriver); - } - - /// - /// Specifies the browser you would like to control - /// Default: Firefox - /// - public Drivers.Browser Browser { get; set; } - - /// - /// Specifies the driver you would like to use to control the browser - /// Default: SeleniumWebDriver - /// - public Type Driver { get; set; } - - - /// - /// The host of the website you are testing, e.g. 'github.com' - /// Default: localhost - /// - public string AppHost - { - get { return appHost;} - set - { - if (Uri.IsWellFormedUriString(value, UriKind.Absolute)) - { - var uri = new Uri(value); - SSL = uri.Scheme == "https"; - UserInfo = uri.UserInfo; - value = uri.Host; - } - appHost = value?.TrimEnd('/'); - } - } - - internal string UserInfo { get; set; } - - /// - /// The port of the website you are testing - /// Default: 80 - /// - public int Port { get; set; } - - /// - /// Whether to use the HTTPS protocol to connect to website you are testing - /// Default: false - /// - public bool SSL { get; set; } - } -} \ No newline at end of file +using System; +using Coypu.Drivers.Playwright; +using Coypu.Drivers.Selenium; +using OpenQA.Selenium.DevTools.V85.HeadlessExperimental; + +namespace Coypu +{ + /// + /// Global configuration settings + /// + public class SessionConfiguration : Options + { + const string DEFAULT_APP_HOST = "localhost"; + const int DEFAULT_PORT = 80; + + private string appHost; + + /// + /// New default configuration + /// + public SessionConfiguration() + { + AppHost = DEFAULT_APP_HOST; + Port = DEFAULT_PORT; + SSL = false; + Browser = Drivers.Browser.Firefox; + Driver = typeof (SeleniumWebDriver); + Headless = true; + } + + /// + /// Specifies the browser you would like to control + /// Default: Firefox + /// + public Drivers.Browser Browser { get; set; } + + /// + /// Specifies whether the browser should run in headless mode + /// Default: true + /// + public bool Headless { get; set; } + + /// + /// Specifies the driver you would like to use to control the browser + /// Default: SeleniumWebDriver + /// + public Type Driver { get; set; } + + + /// + /// The host of the website you are testing, e.g. 'github.com' + /// Default: localhost + /// + public string AppHost + { + get { return appHost;} + set + { + if (Uri.IsWellFormedUriString(value, UriKind.Absolute)) + { + var uri = new Uri(value); + SSL = uri.Scheme == "https"; + UserInfo = uri.UserInfo; + value = uri.Host; + } + appHost = value?.TrimEnd('/'); + } + } + + internal string UserInfo { get; set; } + + /// + /// The port of the website you are testing + /// Default: 80 + /// + public int Port { get; set; } + + /// + /// Whether to use the HTTPS protocol to connect to website you are testing + /// Default: false + /// + public bool SSL { get; set; } + } +} diff --git a/src/Coypu/SnapshotElementScope.cs b/src/Coypu/SnapshotElementScope.cs index db50c90e..de6aae23 100644 --- a/src/Coypu/SnapshotElementScope.cs +++ b/src/Coypu/SnapshotElementScope.cs @@ -1,38 +1,38 @@ -using System; +using System; using Coypu.Actions; using Coypu.Queries; namespace Coypu { /// - /// The scope of an element already found in the document, therefore not deferred. - /// - /// If this element becomes stale then using this scope will not try to refind the element but + /// The scope of an element already found in the document, therefore not deferred. + /// + /// If this element becomes stale then using this scope will not try to refind the element but /// will raise a MissingHtmlException immediately. /// public class SnapshotElementScope : ElementScope { - private readonly Element element; - private readonly Options options; - - internal SnapshotElementScope(Element element, DriverScope scope, Options options) - : base(null, scope) - { - this.element = element; - this.options = options; - } - - internal override bool Stale - { - get => true; - set {} - } - - protected internal override Element FindElement() - { - return element; - } - + private readonly Element element; + private readonly Options options; + + internal SnapshotElementScope(Element element, DriverScope scope, Options options) + : base(null, scope) + { + this.element = element; + this.options = options; + } + + internal override bool Stale + { + get => true; + set {} + } + + protected internal override Element FindElement() + { + return element; + } + internal override void Try(DriverAction action) { action.Act(); @@ -41,21 +41,22 @@ internal override void Try(DriverAction action) internal override bool Try(Query query) { return query.Run(); - } - - internal override T Try(Func getAttribute) - { - return getAttribute(); - } - - public override bool Exists(Options options = null) - { - return FindXPath(".", options).Exists(); - } - - public override bool Missing(Options options = null) - { - return FindXPath(".", options).Missing(); + } + + internal override T Try(Func getAttribute) + { + return getAttribute(); + } + + public override bool Exists(Options options = null) + { + return FindXPath(".", options).Exists(); + } + + public override bool Missing(Options options = null) + { + var scope = FindXPath(".", options); + return scope.Missing(); } } -} \ No newline at end of file +} diff --git a/src/Coypu/Timing/RetryUntilTimeoutTimingStrategy.cs b/src/Coypu/Timing/RetryUntilTimeoutTimingStrategy.cs index aa005b1c..fdcd4d7e 100644 --- a/src/Coypu/Timing/RetryUntilTimeoutTimingStrategy.cs +++ b/src/Coypu/Timing/RetryUntilTimeoutTimingStrategy.cs @@ -1,123 +1,135 @@ -using System; -using System.Diagnostics; -using System.Threading; -using Coypu.Actions; -using Coypu.Queries; -using OpenQA.Selenium; - -namespace Coypu.Timing -{ - public class RetryUntilTimeoutTimingStrategy : TimingStrategy - { - public void TryUntil(BrowserAction tryThis, PredicateQuery until, Options options) - { - var outcome = Synchronise(new ActionSatisfiesPredicateQuery(tryThis, until, options, this)); - if (!outcome) - throw new MissingHtmlException("Timeout from TryUntil: the page never reached the required state."); - } - - public bool ZeroTimeout { get; set; } - private TimeSpan? overrideTimeout; - - public void SetOverrideTimeout(TimeSpan timeout) - { - overrideTimeout = timeout; - } - - public void ClearOverrideTimeout() - { - overrideTimeout = null; - } - - public TResult Synchronise(Query query) - { - var interval = query.Options.RetryInterval; - var stopWatch = Stopwatch.StartNew(); - while (true) - { - - try - { - var result = query.Run(); - if (ExpectedResultNotFoundWithinTimeout(query.ExpectedResult, result, stopWatch, Timeout(query), - interval)) - { - WaitForInterval(interval); - continue; - } - return result; - } - catch (NotSupportedException) - { - throw; - } - catch (UnhandledAlertException) - { - // Could come from anywhere. Throw straight up rather than retrying as requires user interaction. - throw; - } - catch (FinderException ex) - { - if (TimeoutReached(stopWatch, Timeout(query), interval)) - throw ex; - - WaitForInterval(interval); - } - catch (Exception ex) - { - MarkAsStale(query); - if (TimeoutReached(stopWatch, Timeout(query), interval)) - throw ex; - - WaitForInterval(interval); - } - } - } - - private static void MarkAsStale(Query query) - { - if (query.Scope == null) - return; - - if (query.Scope.Stale && query.Scope.OuterScope != null) - query.Scope.OuterScope.Stale = true; - else - query.Scope.Stale = true; - } - - private TimeSpan Timeout(Query query) - { - TimeSpan timeout; - if (ZeroTimeout) - { - timeout = TimeSpan.Zero; - } - else if (overrideTimeout.HasValue) - { - timeout = overrideTimeout.Value; - } - else - { - timeout = query.Options.Timeout; - } - return timeout; - } - - private void WaitForInterval(TimeSpan interval) - { - Thread.Sleep(interval); - } - - private bool ExpectedResultNotFoundWithinTimeout(object expectedResult, TResult result, Stopwatch stopWatch, TimeSpan timeout, TimeSpan interval) - { - return expectedResult != null && !result.Equals(expectedResult) && !TimeoutReached(stopWatch, timeout, interval); - } - - private bool TimeoutReached(Stopwatch stopWatch, TimeSpan timeout, TimeSpan interval) - { - var elapsedTimeToNextCall = TimeSpan.FromMilliseconds(stopWatch.ElapsedMilliseconds) + interval; - var timeoutReached = elapsedTimeToNextCall >= timeout; - return timeoutReached; - } - } -} \ No newline at end of file +using System; +using System.Diagnostics; +using System.Threading; +using Coypu.Actions; +using Coypu.Queries; +using OpenQA.Selenium; + +namespace Coypu.Timing +{ + public class RetryUntilTimeoutTimingStrategy : TimingStrategy + { + public void TryUntil(BrowserAction tryThis, PredicateQuery until, Options options) + { + var outcome = Synchronise(new ActionSatisfiesPredicateQuery(tryThis, until, options, this)); + if (!outcome) + throw new MissingHtmlException("Timeout from TryUntil: the page never reached the required state."); + } + + /// + /// Retries the specified pop-up action until it succeeds or times out. + /// + /// The pop-up action to retry. + /// The options for the retry. + public static void Retry(Action popUpAction, Options options = null) + { + new RetryUntilTimeoutTimingStrategy().Synchronise( + new LambdaBrowserAction(popUpAction, options ?? new Options()) + ); + } + + public bool ZeroTimeout { get; set; } + private TimeSpan? overrideTimeout; + + public void SetOverrideTimeout(TimeSpan timeout) + { + overrideTimeout = timeout; + } + + public void ClearOverrideTimeout() + { + overrideTimeout = null; + } + + public TResult Synchronise(Query query) + { + var interval = query.Options.RetryInterval; + var stopWatch = Stopwatch.StartNew(); + while (true) + { + + try + { + var result = query.Run(); + if (ExpectedResultNotFoundWithinTimeout(query.ExpectedResult, result, stopWatch, Timeout(query), + interval)) + { + WaitForInterval(interval); + continue; + } + return result; + } + catch (NotSupportedException) + { + throw; + } + catch (UnhandledAlertException) + { + // Could come from anywhere. Throw straight up rather than retrying as requires user interaction. + throw; + } + catch (FinderException ex) + { + if (TimeoutReached(stopWatch, Timeout(query), interval)) + throw ex; + + WaitForInterval(interval); + } + catch (Exception ex) + { + MarkAsStale(query); + if (TimeoutReached(stopWatch, Timeout(query), interval)) + throw ex; + + WaitForInterval(interval); + } + } + } + + private static void MarkAsStale(Query query) + { + if (query.Scope == null) + return; + + if (query.Scope.Stale && query.Scope.OuterScope != null) + query.Scope.OuterScope.Stale = true; + else + query.Scope.Stale = true; + } + + private TimeSpan Timeout(Query query) + { + TimeSpan timeout; + if (ZeroTimeout) + { + timeout = TimeSpan.Zero; + } + else if (overrideTimeout.HasValue) + { + timeout = overrideTimeout.Value; + } + else + { + timeout = query.Options.Timeout; + } + return timeout; + } + + private void WaitForInterval(TimeSpan interval) + { + Thread.Sleep(interval); + } + + private bool ExpectedResultNotFoundWithinTimeout(object expectedResult, TResult result, Stopwatch stopWatch, TimeSpan timeout, TimeSpan interval) + { + return expectedResult != null && !result.Equals(expectedResult) && !TimeoutReached(stopWatch, timeout, interval); + } + + private bool TimeoutReached(Stopwatch stopWatch, TimeSpan timeout, TimeSpan interval) + { + var elapsedTimeToNextCall = TimeSpan.FromMilliseconds(stopWatch.ElapsedMilliseconds) + interval; + var timeoutReached = elapsedTimeToNextCall >= timeout; + return timeoutReached; + } + } +} diff --git a/src/Coypu/WebRequests/WebRequestCookieInjector.cs b/src/Coypu/WebRequests/WebRequestCookieInjector.cs index 84ea8c84..1e8ec913 100644 --- a/src/Coypu/WebRequests/WebRequestCookieInjector.cs +++ b/src/Coypu/WebRequests/WebRequestCookieInjector.cs @@ -25,4 +25,4 @@ internal static HttpWebRequest AddCookiesToCookieContainer(HttpWebRequest httpRe return httpRequest; } } -} \ No newline at end of file +}