ryan pollard ryan pollard world dylan just dylan
play

Ryan Pollard ryan.pollard.world Dylan Just dylan@techtangents.com - PowerPoint PPT Presentation

Ryan Pollard ryan.pollard.world Dylan Just dylan@techtangents.com Flax A good source of Selenium The Problem Legacy product needed testing. Scala codebase. Product UI was very complex. Needed simple tests. Goals Nice,


  1. Ryan Pollard ryan.pollard.world Dylan Just dylan@techtangents.com

  2. Flax A good source of Selenium

  3. The Problem ● Legacy product needed testing. ● Scala codebase. ● Product UI was very complex. ● Needed simple tests.

  4. Goals ● Nice, high-level language. ● Usable by testers. ● Clean syntax - readable by non-testers. ● Feedback on failures. ● To FP or not to FP?

  5. !|script |com.ephox.webradar.fitnesse.fixtures.WebRadarFixture | |open url |http://myserver:10039/wps/portal | |click link having text |Log In | |enter text |username |in textbox having id|userID | |enter text |mypassword |in textbox having id|password | |click element having id |login.button.login | |verify text |wpsadmin |appears on page |

  6. Anyone promoting tools where you can ‘program without programming’ is just selling you a terrible programming language.

  7. Let's use a programming language!!!

  8. Let's use a programming language!!!

  9. Selenium Java Client ● No effect-tracking. ● Exceptions and nulls. ● Mutable data. ● Messy syntax. ● Java.

  10. Selenium Java Client

  11. Selenium Java Client Passing Driver around! Not reachable? Not found? Could return null/exception?

  12. Implicits def changeLocale(locale: Locale) (implicit config : TestConfig, driver : WebDriver): Unit = { goToUserSettings typeInCurrentPassword setLocale(locale) clickOk waitForPageToGoAway Exceptions }

  13. Different Shapes

  14. FP

  15. FP

  16. FP

  17. Flax Attacks

  18. Inside the Flax Shack ● Import Specs2. ● Import Flax. ● Make tests go now.

  19. Flax Strikes Back github.com/idempotency/investo

  20. Thanks, Functional Programming. Thunktional Programming.

  21. Code that describes things FP Code that does things

  22. Describe: Testing with Selenium

  23. We need a Selenium WebDriver object

  24. Side effects: browser interaction

  25. Logs: what actions were performed?

  26. Tests may pass or fail

  27. Read values from the browser A

  28. A

  29. A

  30. "Action"

  31. An Action takes a WebDriver as input.

  32. Reader case class Reader[T, A] = Reader(f: T => A) Reader[Driver, A] Reader[T, A] T => A

  33. An Action may perform effects on the browser

  34. IO* def apply[A](a: => A): IO[A] IO[A] ! unsafePerformIO: A A

  35. An Action may produce log messages.

  36. Writer case class Writer[W, A] = Writer (run: (W, A)) Writer[Log[String], A] Writer[W, A] W write / accumulate return A

  37. An Action may pass or fail.

  38. Either Err \/ A Err • Test Assertion Failed • Could Not Find Element • Wrong Element Type • Kersploded • Other

  39. A

  40. IO Reader Writer Either A

  41. IO Reader Writer Either A

  42. EitherT WriterT ReaderT IO A

  43. Monad EitherT Transformers WriterT form monads ReaderT for stacks of IO monadic things

  44. case class Action(run: EitherT[ WriterT[ ReaderT[IO, Driver, ?], Log[String], ?], Err, A ])

  45. What do we want to do with Actions? ● Create them. ● "Chain" them together ● Execute them as tests.

  46. Create ● logOnly (String) ● fromSideEffect ((d: Driver) => ...) ● assert (x must_=== y) ● find (By) ● click (Elem)

  47. Create def fromSideEffectWithLog [A]( msg : String, run : Driver => A): Action[A] = … def get (url: String): Action[Unit] = fromSideEffectWithLog ( "Opening URL: " + url , driver => driver .d. get ( url ) )

  48. Chaining actions?

  49. Monading def setTextBy(by: By, text: String): Action[Unit] = for { e <- find(by) _ <- clear(e) _ <- typeIn(e, text) _ <- click("submit") _ < assert } yield ()

  50. Stack traces vs Logs

  51. Executing driver.get( "https://twitter.com/" ); driver.findElement(By. xpath ( "//input[@name='session[username_or_email]']" )) .sendKeys(username); driver.findElement(By. xpath ( "//input[@name='session[password]']" )) .sendKeys(password); driver.findElement(By. xpath ( "//input[@value='Log on']" )).click(); doSomethingElse(); *** Element info: {Using=xpath, value=//input[@value='Log on']} ... at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:545) at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:319) at org.openqa.selenium.remote.RemoteWebDriver.findElementByXPath(RemoteWebDriver.java:421) at org.openqa.selenium.By$ByXPath.findElement(By.java:361) at org.openqa.selenium.remote.RemoteWebDriver.findElement(RemoteWebDriver.java:311) at com.ephox.flax.it.MyTest.main(MyTest.java:20)

  52. Composing "My test" >> { for { _ <- get ( "https://twitter.com/" ) _ <- typeInBy ( xpath ( "//input[@name='session[username_or_email]']" ), username ) _ <- typeInBy ( xpath ( "//input[@name='session[password]']" ), password ) _ <- clickBy ( xpath ( "//input[@value='Log in']" )) } yield () } ... a.runAsResult(driver) Executing

  53. Log Action final case class Log[A](list: DList[Node[A]]) final case class Node[A](label: A, children: Log[A]) Log Nodes

  54. logOnly("pen") pen

  55. logOnly("apple") logOnly("pen") apple pen

  56. *>

  57. logOnly("apple") *> logOnly("pen") apple pen

  58. nested ( "Open twitter homepage" , get ( "https://twitter.com/" )) Open twitter homepage Opening URL: https://twitter.com/

  59. nested ( "Enter Credentials" , for { _ <- typeInBy ( xpath ( "//...." ), username ) _ <- typeInBy ( xpath ( "//...." ), password ) } Enter Credentials Type in by xpath... Type in by xpath...

  60. def signInToTwitter: Action[Unit] = nested ( "Sign in to Twitter" , for { _ <- openTwitterHomepage _ <- enterCredentials _ <- clickLoginButton } yield ()) def openTwitterHomepage: Action[Unit] = nested ( "Open twitter homepage" , get ( "https://twitter.com/" )) def enterCredentials: Action[Unit] = nested ( "Enter Credentials" , for { _ <- typeInBy ( xpath ( "//input[@name='session[username_or_email]']" ), username ) _ <- typeInBy ( xpath ( "//input[@name='session[password]']" ), password ) } yield ()) def clickLoginButton: Action[Unit] = nested ( "Click login button" , clickBy ( xpath ( "//input[@value=' Lorg in ']" )))

  61. Steps performed: - Signing in to Twitter - Open twitter homepage - Opening URL: https://twitter.com/ - Enter Credentials - Finding element: By.xpath: //input[@name='session[username_or_email]'] - Typing "flax_demo" in element: By.xpath: //input[@name='session[username_or_email]'] - Finding element: By.xpath: //input[@name='session[password]'] - Typing "flaxtwitter" in element: By.xpath: //input[@name='session[password]'] - Click login button - Finding element: By.xpath: //input[@value='Lorg in'] !!! Could not find element: By.xpath: //input[@value='Lorg in']

  62. Conclusion ● FP gave practical benefits. ● Scala "for" comprehensions. ● Monad transformers - hard in Scala, but helpful ● Log tree. ● FP data types modelled key aspects of behaviour.

  63. Ryan Pollard ryan.pollard.world Dylan Just dylan@techtangents.com

  64. Workshop 1. Set up Intellij and sbt shell command. 2. "Getting started" instructions. 3. Test some things. Things to try: a. Open a site b. Click a button c. Type text d. Nest actions Example scenarios follow...

  65. Test ephox.com https://www.ephox.com/ Click "compare rich text editors" Click the "learn more" link - this takes us to tinymce.com Execute some js to get the content from the tinymce instance Get the "quick start" html text.

  66. Test docs https://www.tinymce.com/docs/ Search for "hybrid". Click on the links.

  67. assertions Use FlaxAssertions.assert to assert some simple properties on the previous examples. Observe test passes and failures.

  68. Implementing Actions Check out the Flax code from github Implement the api from the selenium "Select" in the "Selekt" wrapper class. See "Elem" for an example. Send a PR :)

Recommend


More recommend