Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The State of Front-End Testing

The State of Front-End Testing

Web Directions | Code 16
Sydney, July 29 2016
Melbourne, August 2 2016

Alicia Sedlock

July 29, 2016
Tweet

More Decks by Alicia Sedlock

Other Decks in Technology

Transcript

  1. • Web Developer @
 Society of Grownups • @aliciability on

    Twitter • Teacher with Girl Develop It • Run Hedgehogs & Reflogs
  2. #frontendtesting As developers we don’t want to • Give users

    broken products • Break trust with QA & business teams • Waste development time
  3. #frontendtesting Front-end testing is… • A collection of techniques to

    hold developers accountable for writing and maintaining functioning and usable code bases.
  4. #frontendtesting The Beginnings of Testing • Hi, server-side developers! •

    Here comes the client-side! • Opportunities galore and (sometimes neglected) responsibilities
  5. #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  6. #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  7. #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  8. #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  9. #frontendtesting describe(“Calculator Operations”, function () { it(“Should add two numbers”,

    function () { Calculator.init(); var result = Calculator.addNumbers(7,3); expect(result).toBe(10); }); });
  10. #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  11. #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  12. #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  13. #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  14. #frontendtesting it(“Should remember the last calculation”, function () { spyOn(Calculator,

    “updateCurrentValue”); Calculator.addNumbers(7,10); expect(Calculator.updateCurrentValue).toHaveBeenCalled(); expect(Calculator.updateCurrentValue) .toHaveBeenCalledWith(17); expect(Calculator.currentValue).toBe(17); });
  15. #frontendtesting Some big players • Jasmine • Mocha • Chai

    • Webdriver.io • QUnit • Unit.js • Sinon
  16. #frontendtesting test('it renders completed class properly', function(assert) { set(this, 'currentStepIndex',

    0); set(this, 'step', { index: 1 }); this.render(hbs`{{progress-bar/progress-step this}}`); assert.notOk(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is not marked complete'); run(() => { set(this, 'currentStepIndex', 1); }); assert.ok(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is marked complete'); });
  17. #frontendtesting test('it renders completed class properly', function(assert) { set(this, 'currentStepIndex',

    0); set(this, 'step', { index: 1 }); this.render(hbs`{{progress-bar/progress-step this}}`); assert.notOk(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is not marked complete'); run(() => { set(this, 'currentStepIndex', 1); }); assert.ok(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is marked complete'); });
  18. #frontendtesting test('it renders completed class properly', function(assert) { set(this, 'currentStepIndex',

    0); set(this, 'step', { index: 1 }); this.render(hbs`{{progress-bar/progress-step this}}`); assert.notOk(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is not marked complete'); run(() => { set(this, 'currentStepIndex', 1); }); assert.ok(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is marked complete'); });
  19. #frontendtesting test('it renders completed class properly', function(assert) { set(this, 'currentStepIndex',

    0); set(this, 'step', { index: 1 }); this.render(hbs`{{progress-bar/progress-step this}}`); assert.notOk(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is not marked complete'); run(() => { set(this, 'currentStepIndex', 1); }); assert.ok(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is marked complete'); });
  20. #frontendtesting test('it renders completed class properly', function(assert) { set(this, 'currentStepIndex',

    0); set(this, 'step', { index: 1 }); this.render(hbs`{{progress-bar/progress-step this}}`); assert.notOk(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is not marked complete'); run(() => { set(this, 'currentStepIndex', 1); }); assert.ok(this.$(‘.progress-bar__step').hasClass('progress-bar__step--completed'), 'step is marked complete'); });
  21. #frontendtesting describe(“Acceptance tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure state”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  22. #frontendtesting describe(“Acceptance tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure State ”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  23. #frontendtesting describe(“Acceptance tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure state”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  24. #frontendtesting describe(“Acceptance tests”, function () { var page; beforeEach(function (done)

    { page = visit(“/home”); page.ready(done); }); describe(“Sign Up Failure state”, function () { beforeEach(function (done) { page.fill_in(“input[name='email']", “Not An Email”); page.click(“button[type=submit]”); page.onBodyChange(done); }); it(“Shouldn’t allow signup with invalid information”, function () { expect(page.find(“#signupError”) .hasClass(“hidden”)).toBeFalsy(); }); }); });
  25. #frontendtesting The Evolution • Front-end development is more than just

    business logic • There are other parts of the code that can fail • Apply the tactic of testing expected outcomes
  26. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(“#signUpForm”, { name: “Alicia Sedlock”, email: “[email protected]” }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  27. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  28. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  29. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  30. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  31. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  32. #frontendtesting casper.start(“/home”).then(function(){ // Initial state of form phantomcss .screenshot(“#signUpForm”, “sign

    up form”); // Hit the sign up button (should trigger error) casper.click(“button#signUp”); // Take a screenshot of the UI component phantomcss .screenshot(“#signUpForm”, “sign up form error”); // Fill in form by name attributes & submit casper.fill(‘#signUpForm', { name: ‘Alicia Sedlock’, email: ‘[email protected]’ }, true); // Take a second screenshot of success state phantomcss .screenshot(“#signUpForm”, “sign up form success”); });
  33. #frontendtesting casper.start(“/styleguide”).then(function(){ phantomcss .screenshot(“a.link”, “link example”); phantomcss .screenshot(“.btn-primary”, “primary button”);

    }).then(function() { this.mouse.move(“.btn-primary”'); phantomcss .screenshot(“.btn-primary”, “button hover”); });
  34. #frontendtesting phantomcss: { options: { mismatchTolerance: 0.05, screenshots: ‘tests/visual/baselines’, results:

    ‘tests/visual/results’, viewportSize: [1280, 800] }, src: [‘tests/visual/tests.js’] }
  35. #frontendtesting phantomcss: { big_size: { options: { ... viewportSize: [1280,

    800] } }, small_size: { options: { ... viewportSize: [480, 320] } } }
  36. #frontendtesting Tools I’ve heard of • a11y • use grunt-a11y

    for build tool automation • Pa11y • ember-a11y-testing • protractor-accessibility-plugin (Angular) • react-a11y
  37. #frontendtesting perfbudget: { default: { options: { url: [your site’s

    URL], budget: { visualComplete: '3000', SpeedIndex: ‘1500’, render: ‘500’, fullyLoaded: ‘3000’, requests: ’25’, bytesInDoc: '100000' } } } }
  38. #frontendtesting gremlin formFiller input 5 in <input type="number" name="age"> gremlin

    formFiller input [email protected] in <input type="email"> gremlin clicker click at 1219 301 gremlin scroller scroll to 100 25 ... mogwai fps 33.21 mogwai fps 59.45 mogwai fps 12.67 err > mogwai fps 7.54 < err
  39. #frontendtesting Assessment • How much business logic are we off-loading

    to the client-side? • What is our history with regressions? • Are we constantly fixing, and re-fixing, visual/UI issues? • In general, which piece of our stack is the most instable/Jenga-like? • Is there a lot of inconsistency in how we write code?
  40. This is more code to write, and my code will

    still have bugs. #frontendtesting