Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Angular connect. Testing angular apps. at scale.

370 views

Published on

Angular became the framework of choice for numerous large-scale projects in the media, e-commerce, financial and banking sectors to name but a few.

However, even though the framework itself has changed and improved dramatically since it was originally introduced, the way we design the acceptance tests and the design patterns we use to do that is still stuck in 2009.

This makes it difficult to write E2E tests and use Protractor at scale, especially on projects where multiple teams are involved. In this talk I will demonstrate Serenity/JS, a TypeScript implementation of the popular Serenity BDD library, that lets you test Angular apps using the Screenplay Pattern and benefit from the powerful reporting features provided by Serenity BDD.

Combining the advantages of Protractor, the expressive Screenplay DSL, tooling support for TypeScript, and the powerful reporting features of Serenity BDD, Serenity/JS is set to become the tool of choice for your Angular automated testing!

Published in: Software
  • Be the first to comment

Angular connect. Testing angular apps. at scale.

  1. 1. serenity-js.org#SerenityJS @JanMolak Testing Angular apps.
 At scale.
  2. 2. serenity-js.org#SerenityJS @JanMolak Jan Molakconsultant, trainer, lead developer of Serenity/JS
  3. 3. serenity-js.org#SerenityJS @JanMolak
  4. 4. serenity-js.org#SerenityJS @JanMolak
  5. 5. serenity-js.org#SerenityJS @JanMolak We ❤ E2E
  6. 6. serenity-js.org#SerenityJS @JanMolak We 💔 E2E (said no one ever)
  7. 7. serenity-js.org#SerenityJS @JanMolak Execution time: minutes → hours
  8. 8. serenity-js.org#SerenityJS @JanMolak Execution time: minutes → hours Writing: hours → days
  9. 9. serenity-js.org#SerenityJS @JanMolak Execution time: minutes → hours Writing: hours → days Analysis and reporting: hours → days
  10. 10. serenity-js.org#SerenityJS @JanMolak Execution time: minutes → hours Writing: hours → days Analysis and reporting: hours → days Maintenance: days → weeks
  11. 11. serenity-js.org#SerenityJS @JanMolak The difference between
 scalable tests 
 and a world of pain
 is design.
  12. 12. serenity-js.org#SerenityJS @JanMolak Testing the Journey Planner
  13. 13. serenity-js.org#SerenityJS @JanMolak
  14. 14. serenity-js.org#SerenityJS @JanMolak When a commuter wants to travel 
 from Waterloo to Canary Wharf 
 around 9:00 AM they should be told 
 about the 8:59 AM Jubilee Line train.
  15. 15. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
  16. 16. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo
  17. 17. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Enter “Waterloo”
  18. 18. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Enter “Waterloo” Pick the first suggestion
  19. 19. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Enter “Waterloo” Pick the first suggestion Wait for the suggestions
  20. 20. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Enter “Waterloo” Pick the first suggestion Wait for the suggestions Press ▼
  21. 21. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Enter “Waterloo” Pick the first suggestion Wait for the suggestions Press ▼
 Press ↩
  22. 22. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Enter “Waterloo” Pick the first suggestion Wait for the suggestions Press ▼
 Press ↩
  23. 23. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo
  24. 24. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf
  25. 25. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf
  26. 26. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM
  27. 27. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Click on the “change time” link
  28. 28. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Click on the “change time” link Click on the “Leaving” button
  29. 29. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Click on the “change time” link Click on the “Leaving” button Select “09:00”
  30. 30. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM
  31. 31. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Confirm selection
  32. 32. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Confirm selection Click on the “Plan my journey”
  33. 33. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Confirm selection Click on the “Plan my journey” Wait for the results to load
  34. 34. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Confirm selection
  35. 35. serenity-js.org#SerenityJS @JanMolak Open the Journey Planner
 Choose origin: Waterloo Choose destination: Canary Wharf Choose time of departure: 9:00 AM Confirm selection See a Jubilee Line train at 08:59
  36. 36. serenity-js.org#SerenityJS @JanMolak At a high level, it’s just 6 tasks
  37. 37. serenity-js.org#SerenityJS @JanMolak Scripting the flow
  38. 38. serenity-js.org#SerenityJS @JanMolak “Use Protractor to write and run the e2e tests.
 End-to-end tests explore the application
 as users experience it. Protractor makes your scenario tests run faster and in a stable manner.  - Angular website
  39. 39. serenity-js.org#SerenityJS @JanMolak const origin = element(by.id(‘…’)), originSuggestions = element(by.xpath(‘…’)), destination = element(by.id(‘…’)), destinationSuggestions = element(by.xpath(‘…’)), changeTimeLink = element(by.linkText(‘…’)), leavingButton = element(by.css(‘…’)), timeSelector = element(by.id(‘…’)), planMyJourneyButton = element(by.buttonText(‘…’)), journeyResults = element(by.css(‘…’)), fastestJourney = element(by.css(‘…’));
  40. 40. serenity-js.org#SerenityJS @JanMolak browser.get('https://tfl.gov.uk/');
  41. 41. serenity-js.org#SerenityJS @JanMolak browser.get('https://tfl.gov.uk/'); origin.sendKeys('Waterloo');
  42. 42. serenity-js.org#SerenityJS @JanMolak browser.get('https://tfl.gov.uk/'); origin.sendKeys('Waterloo'); browser.wait(
 EC.visibilityOf(originSuggestions), 5000);
  43. 43. serenity-js.org#SerenityJS @JanMolak browser.get('https://tfl.gov.uk/'); origin.sendKeys('Waterloo'); browser.wait(
 EC.visibilityOf(originSuggestions), 5000); origin.sendKeys(protractor.Key.ARROW_DOWN); origin.sendKeys(protractor.Key.ENTER);
  44. 44. serenity-js.org#SerenityJS @JanMolak browser.get('https://tfl.gov.uk/'); origin.sendKeys('Waterloo'); browser.wait(
 EC.visibilityOf(originSuggestions), 5000); origin.sendKeys(protractor.Key.ARROW_DOWN); origin.sendKeys(protractor.Key.ENTER); destination.sendKeys('Canary Wharf'); browser.wait(
 EC.visibilityOf(destinationSuggestions), 5000); destination.sendKeys(protractor.Key.ARROW_DOWN); destination.sendKeys(protractor.Key.ENTER);
  45. 45. serenity-js.org#SerenityJS @JanMolak changeTimeLink.click(); leavingButton.click(); timeSelector.element(
 by.cssContainingText('option', '09:00')
 ).click();
  46. 46. serenity-js.org#SerenityJS @JanMolak changeTimeLink.click(); leavingButton.click(); timeSelector.element(
 by.cssContainingText('option', '09:00')
 ).click(); planMyJourneyButton.click(); browser.wait(EC.visibilityOf(journeyResults), 5000);
  47. 47. serenity-js.org#SerenityJS @JanMolak fastestJourney.getText() .then(journeyDetailsFromText) .then(option => { expect(option).to.deep.equal({ line: 'Jubilee', departs_at: '08:59', }); });
  48. 48. serenity-js.org#SerenityJS @JanMolak cost origin = element(by.id(‘…’)), originSuggestions = element(by.xpath(‘…’)), destination = element(by.id(‘…’)), destinationSuggestions = element(by.xpath(‘…’)), changeTimeLink = element(by.linkText(‘…’)), leavingButton = element(by.css(‘…’)), timeSelector = element(by.id(‘…’)), planMyJourneyButton = element(by.buttonText(‘…’)), journeyResults = element(by.css(‘…’)), fastestJourney = element(by.css(‘…’)); 
 browser.get('https://tfl.gov.uk/'); origin.sendKeys('Waterloo'); browser.wait(
 EC.visibilityOf(originSuggestions), 5000); origin.sendKeys(protractor.Key.ARROW_DOWN); origin.sendKeys(protractor.Key.ENTER); destination.sendKeys('Canary Wharf'); browser.wait(
 EC.visibilityOf(destinationSuggestions), 5000); destination.sendKeys(protractor.Key.ARROW_DOWN); destination.sendKeys(protractor.Key.ENTER); changeTimeLink.click(); leavingButton.click(); timeSelector.element(
 by.cssContainingText('option', '09:00')
 ).click(); planMyJourneyButton.click(); browser.wait(EC.visibilityOf(journeyResults), 5000); fastestJourney.getText() .then(journeyDetailsFromText) .then(option => { expect(option).to.deep.equal({ line: ‘Jubilee', departs_at: '08:59', }); 6 tasks ?
  49. 49. serenity-js.org#SerenityJS @JanMolak cost origin = element(by.id(‘…’)), originSuggestions = element(by.xpath(‘…’)), destination = element(by.id(‘…’)), destinationSuggestions = element(by.xpath(‘…’)), changeTimeLink = element(by.linkText(‘…’)), leavingButton = element(by.css(‘…’)), timeSelector = element(by.id(‘…’)), planMyJourneyButton = element(by.buttonText(‘…’)), journeyResults = element(by.css(‘…’)), fastestJourney = element(by.css(‘…’)); 
 browser.get('https://tfl.gov.uk/'); origin.sendKeys('Waterloo'); browser.wait(
 EC.visibilityOf(originSuggestions), 5000); origin.sendKeys(protractor.Key.ARROW_DOWN); origin.sendKeys(protractor.Key.ENTER); destination.sendKeys('Canary Wharf'); browser.wait(
 EC.visibilityOf(destinationSuggestions), 5000); destination.sendKeys(protractor.Key.ARROW_DOWN); destination.sendKeys(protractor.Key.ENTER); changeTimeLink.click(); leavingButton.click(); timeSelector.element(
 by.cssContainingText('option', '09:00')
 ).click(); planMyJourneyButton.click(); browser.wait(EC.visibilityOf(journeyResults), 5000); fastestJourney.getText() .then(journeyDetailsFromText) .then(option => { expect(option).to.deep.equal({ line: ‘Jubilee', departs_at: '08:59', }); 6 tasks buried in 36 lines of noise
  50. 50. serenity-js.org#SerenityJS @JanMolak “If you have WebDriver APIs in your test methods, You’re Doing It Wrong! - Simon Stewart, creator of WebDriver
  51. 51. serenity-js.org#SerenityJS @JanMolak A test system needs design
  52. 52. serenity-js.org#SerenityJS @JanMolak Page Objects to the rescue?
  53. 53. serenity-js.org#SerenityJS @JanMolak
  54. 54. serenity-js.org#SerenityJS @JanMolak class JourneyPlanner { // Locators // Actions }
  55. 55. serenity-js.org#SerenityJS @JanMolak class JourneyPlanner { 
 // Locators private originField = by.id('...'); private originSuggestions = by.xpath('...'); private destinationField = by.id('...'); private destinationSuggestions = by.xpath('...'); private changeTimeLink = by.linkText('...'); private leavingButton = by.css('...'); private timeSelector = by.id('...'); private planMyJourneyButton = by.buttonText('...');
  56. 56. serenity-js.org#SerenityJS @JanMolak class JourneyPlanner { 
 // Actions chooseOriginOf(origin: string) { element(this.originField).sendKeys(origin); 
 browser.wait(…);
 element(this.originField).sendKeys(…); element(this.originField).sendKeys(…); } chooseDestinationOf(destination: string) { … }
 chooseTimeOfDeparture(time: string) { … } confirmSelection() { … }
  57. 57. serenity-js.org#SerenityJS @JanMolak const journeyPlanner = new JourneyPlanner();

  58. 58. serenity-js.org#SerenityJS @JanMolak const journeyPlanner = new JourneyPlanner();
 journeyPlanner.get(); journeyPlanner.chooseOriginOf('Waterloo'); journeyPlanner.chooseDestinationOf('Canary Wharf'); journeyPlanner.chooseTimeOfDeparture('09:00'); journeyPlanner.confirmSelection();
  59. 59. serenity-js.org#SerenityJS @JanMolak const journeyPlanner = new JourneyPlanner();
 journeyPlanner.get(); journeyPlanner.chooseOriginOf('Waterloo'); journeyPlanner.chooseDestinationOf('Canary Wharf'); journeyPlanner.chooseTimeOfDeparture('09:00'); journeyPlanner.confirmSelection(); const journeyResults = new JourneyResults();
  60. 60. serenity-js.org#SerenityJS @JanMolak const journeyPlanner = new JourneyPlanner();
 journeyPlanner.get(); journeyPlanner.chooseOriginOf('Waterloo'); journeyPlanner.chooseDestinationOf('Canary Wharf'); journeyPlanner.chooseTimeOfDeparture('09:00'); journeyPlanner.confirmSelection(); const journeyResults = new JourneyResults(); expect(journeyResults.fastestTrain())
 .to.eventually.deep.equal({ line: ‘Jubilee’, …})
  61. 61. serenity-js.org#SerenityJS @JanMolak const journeyPlanner = new JourneyPlanner();
 journeyPlanner.get(); journeyPlanner.chooseOriginOf('Waterloo'); journeyPlanner.chooseDestinationOf('Canary Wharf'); journeyPlanner.chooseTimeOfDeparture('09:00'); journeyPlanner.confirmSelection(); const journeyResults = new JourneyResults(); expect(journeyResults.fastestOption())
 .to.eventually.deep.equal({ line: ‘Jubilee’, …}) not bad!
  62. 62. serenity-js.org#SerenityJS @JanMolak const journeyPlanner = new JourneyPlanner();
 journeyPlanner.get(); journeyPlanner.chooseOriginOf('Waterloo'); journeyPlanner.chooseDestinationOf('Canary Wharf'); journeyPlanner.chooseTimeOfDeparture('09:00'); journeyPlanner.confirmSelection(); const journeyResults = new JourneyResults(); expect(journeyResults.fastestOption())
 .to.eventually.deep.equal({ line: ‘Jubilee’, …}) not bad?
  63. 63. serenity-js.org#SerenityJS @JanMolak class JourneyPlanner { private originField = by.id('...'); private originSuggestions = by.xpath('...'); private destinationField = by.id('...'); private destinationSuggestions = by.xpath('...'); private changeTimeLink = by.linkText('...'); private leavingButton = by.css('...'); private timeSelector = by.id('...');
 private planMyJourneyButton = by.buttonText('...');
 get = () => browser.get('https://tfl.gov.uk/'); chooseOriginOf(origin: string) { element(this.originField).sendKeys(origin); 
 browser.wait(…);
 element(this.originField).sendKeys(…); element(this.originField).sendKeys(…); } chooseDestinationOf(destination: string) { element(this.destinationField).sendKeys(destination); browser.wait(…); 
 element(this.destinationField).sendKeys(…); 
 element(this.destinationField).sendKeys(…); }
 changeJourneyTime() { element(this.changeTimeLink).click(); }
 chooseTimeOfDeparture(time) { element(this.leavingButton).click(); element(this.timeSelector).element(…).click(); } confirmSelection() { element(this.planMyJourneyButton).click();
 }
 large classes
  64. 64. serenity-js.org#SerenityJS @JanMolak class JourneyPlanner { private originField = by.id('...'); private originSuggestions = by.xpath('...'); private destinationField = by.id('...'); private destinationSuggestions = by.xpath('...'); private changeTimeLink = by.linkText('...'); private leavingButton = by.css('...'); private timeSelector = by.id('...');
 private planMyJourneyButton = by.buttonText('...');
 get = () => browser.get('https://tfl.gov.uk/'); chooseOriginOf(origin: string) { element(this.originField).sendKeys(origin); 
 browser.wait(…);
 element(this.originField).sendKeys(…); element(this.originField).sendKeys(…); } chooseDestinationOf(destination: string) { element(this.destinationField).sendKeys(destination); browser.wait(…); 
 element(this.destinationField).sendKeys(…); 
 element(this.destinationField).sendKeys(…); }
 changeJourneyTime() { element(this.changeTimeLink).click(); }
 chooseTimeOfDeparture(time) { element(this.leavingButton).click(); element(this.timeSelector).element(…).click(); } confirmSelection() { element(this.planMyJourneyButton).click();
 }
 large classes bloated methods
  65. 65. serenity-js.org#SerenityJS @JanMolak class JourneyPlanner { private originField = by.id('...'); private originSuggestions = by.xpath('...'); private destinationField = by.id('...'); private destinationSuggestions = by.xpath('...'); private changeTimeLink = by.linkText('...'); private leavingButton = by.css('...'); private timeSelector = by.id('...');
 private planMyJourneyButton = by.buttonText('...');
 get = () => browser.get('https://tfl.gov.uk/'); chooseOriginOf(origin: string) { element(this.originField).sendKeys(origin); 
 browser.wait(…);
 element(this.originField).sendKeys(…); element(this.originField).sendKeys(…); } chooseDestinationOf(destination: string) { element(this.destinationField).sendKeys(destination); browser.wait(…); 
 element(this.destinationField).sendKeys(…); 
 element(this.destinationField).sendKeys(…); }
 changeJourneyTime() { element(this.changeTimeLink).click(); }
 chooseTimeOfDeparture(time) { element(this.leavingButton).click(); element(this.timeSelector).element(…).click(); } confirmSelection() { element(this.planMyJourneyButton).click();
 }
 large classes bloated methods difficult to compose
  66. 66. serenity-js.org#SerenityJS @JanMolak Serenity/JS and the Screenplay Pattern
  67. 67. serenity-js.org#SerenityJS @JanMolak “A good acceptance test
 is user-centred. It reflects a user’s journey
 to accomplish their goal.
  68. 68. serenity-js.org#SerenityJS @JanMolak 
 Open the Journey Planner
 Choose origin of Waterloo Choose destination of Canary Wharf Choose time of departure: 09:00 Confirm selection See if the fastest train departs at 08:59
  69. 69. serenity-js.org#SerenityJS @JanMolak 
 OpenJourneyPlanner(), ChooseOriginOf(‘Waterloo’), ChooseDestinationOf(‘Canary Wharf’), ChooseTimeOfDeparture(‘09:00’), ConfirmSelection(), See.if(FastestTrain(), departsAt(‘08:59’)),
  70. 70. serenity-js.org#SerenityJS @JanMolak “A task is a composition
 of interactions with the system, performed by an actor
 to accomplish a certain goal.
  71. 71. serenity-js.org#SerenityJS @JanMolak Designing a task
  72. 72. serenity-js.org#SerenityJS @JanMolak ChooseOriginOf(‘Waterloo’)
  73. 73. serenity-js.org#SerenityJS @JanMolak const ChooseOriginOf = (station: string) => Task
  74. 74. serenity-js.org#SerenityJS @JanMolak import { Task } from ‘serenity-js/protractor’ const ChooseOriginOf = (station: string) => Task
  75. 75. serenity-js.org#SerenityJS @JanMolak import { Task } from ‘serenity-js/protractor’ const ChooseOriginOf = (station: string) => Task.where(`#actor selects origin of ${station}`, );
  76. 76. serenity-js.org#SerenityJS @JanMolak import { Enter, Task } from ‘serenity-js/protractor’ const ChooseOriginOf = (station: string) => Task.where(`#actor selects origin of ${station}`, Enter.theValue(station).into(Planner.Origin),
 );
  77. 77. serenity-js.org#SerenityJS @JanMolak import { Enter, Task } from ‘serenity-js/protractor’ const ChooseOriginOf = (station: string) => Task.where(`#actor selects origin of ${station}`, Enter.theValue(station).into(Planner.Origin), PickFirstSuggestionFrom(Planner.Origin_Suggestions), );
  78. 78. serenity-js.org#SerenityJS @JanMolak Actors perform tasks
  79. 79. serenity-js.org#SerenityJS @JanMolak import { Actor } from ‘serenity-js/protractor’; const Connie = Actor.named(‘Connie’);
  80. 80. serenity-js.org#SerenityJS @JanMolak import { Actor } from ‘serenity-js/protractor’; const Connie = Actor.named(‘Connie’);
 
 Connie.attemptsTo( // ... 
 );
  81. 81. serenity-js.org#SerenityJS @JanMolak const Connie = Actor.named(‘Connie’);
 
 Connie.attemptsTo(
 OpenJourneyPlanner(), ChooseOriginOf(‘Waterloo’), ChooseDestinationOf(‘Canary Wharf’), ChooseTimeOfDeparture(‘09:00’), ConfirmSelection(),
 );
  82. 82. serenity-js.org#SerenityJS @JanMolak Actors have abilities 
 that enable interactions with the system
  83. 83. serenity-js.org#SerenityJS @JanMolak import { protractor } from ‘protractor’;
 import { Actor, BrowseTheWeb } from ‘serenity-js/protractor’;
 
 const Connie = Actor
 .named(‘Connie’) .whoCan(BrowseTheWeb.using(protractor.browser));
  84. 84. serenity-js.org#SerenityJS @JanMolak Actors ask questions and verify the answers
  85. 85. serenity-js.org#SerenityJS @JanMolak 
 actor.attemptsTo( See.if(
 question: Question<T>, assertion: Assertion<T>,
 ) );
  86. 86. serenity-js.org#SerenityJS @JanMolak import { Text } from ‘serenity-js/protractor’; actor.attemptsTo( See.if(
 Text.of(Planner.Header), equals(‘Plan a Journey’)
 ) );
  87. 87. serenity-js.org#SerenityJS @JanMolak import { SelectedValue } from ‘serenity-js/protractor’; 
 actor.attemptsTo( See.if(
 SelectedValue.of(Planner.Time_Selector), equals(‘09:00’)
 ) );
  88. 88. serenity-js.org#SerenityJS @JanMolak 
 actor.attemptsTo( See.if(
 FastestTrain(), departsAt(‘08:59’),
 ) );
  89. 89. serenity-js.org#SerenityJS @JanMolak The end result
  90. 90. serenity-js.org#SerenityJS @JanMolak const Connie = Actor.named(‘Connie’) .whoCan(BrowseTheWeb.using(protractor.browser)); 
 Connie.attemptsTo(
 OpenJourneyPlanner(), ChooseOriginOf(‘Waterloo’), ChooseDestinationOf(‘Canary Wharf’), ChooseTimeOfDeparture(‘09:00’), ConfirmSelection(), See.if(FastestTrain(), departsAt(‘08:59’))
 );
  91. 91. serenity-js.org#SerenityJS @JanMolak const Connie = Actor.named(‘Connie’) .whoCan(BrowseTheWeb.using(protractor.browser)); 
 Connie.attemptsTo(
 PlanAJourney.from(‘Waterloo’).to(‘Canary Wharf’).
 departingAt(‘09:00’), // …
 );
  92. 92. serenity-js.org#SerenityJS @JanMolak Reporting
  93. 93. serenity-js.org#SerenityJS @JanMolak
  94. 94. serenity-js.org#SerenityJS @JanMolak
  95. 95. serenity-js.org#SerenityJS @JanMolak Questions to ask: - How much time do I spend maintaining the tests? - Am I modelling user’s interactions with the system, 
 or how the system is built? - Can I share elements of my testing framework with others? - How much duplication and dead code is there in my tests? - How long would it take me to find the issue if a test fails? - How long to change the tests when the system changes? - Can I use my tests to drive the design of the system?
  96. 96. serenity-js.org#SerenityJS @JanMolak Make your tests
 scalable by design
  97. 97. serenity-js.org#SerenityJS @JanMolak Thank you! Learn more at serenity-js.org
 
 janmolak.com
 jan.molak@serenity.io
 twitter.com/JanMolak linkedin.com/in/janmolak github.com/jan-molak

×