Slide 1

Slide 1 text

The State of Front-End Testing Web Directions | Code 16

Slide 2

Slide 2 text

#frontendtesting

Slide 3

Slide 3 text

• Web Developer @
 Society of Grownups • @aliciability on Twitter • Teacher with Girl Develop It • Run Hedgehogs & Reflogs

Slide 4

Slide 4 text

#frontendtesting

Slide 5

Slide 5 text

#frontendtesting As developers we don’t want to • Give users broken products • Break trust with QA & business teams • Waste development time

Slide 6

Slide 6 text

What is “front-end testing”, anyway? #frontendtesting

Slide 7

Slide 7 text

#frontendtesting Front-end testing is… • A collection of techniques to hold developers accountable for writing and maintaining functioning and usable code bases.

Slide 8

Slide 8 text

#frontendtesting The Beginnings of Testing • Hi, server-side developers! • Here comes the client-side! • Opportunities galore and (sometimes neglected) responsibilities

Slide 9

Slide 9 text

I’m not here to prescribe. #frontendtesting

Slide 10

Slide 10 text

A focus on outcome, not the input. #frontendtesting

Slide 11

Slide 11 text

Automation is your friend! #frontendtesting

Slide 12

Slide 12 text

Components of front-end testing #frontendtesting

Slide 13

Slide 13 text

#frontendtesting Visual
 Regression Performance Accessibility Unit Integration Acceptance

Slide 14

Slide 14 text

#frontendtesting Visual Regression Performance Accessibility Unit Integration Acceptance

Slide 15

Slide 15 text

#frontendtesting Unit tests are for ensuring small pieces of code work as expected.

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

#frontendtesting describe(“Calculator Operations”, function () { beforeEach(function () { Calculator.init(); }); ... afterEach(function () { Calculator.teardown(); }); });

Slide 22

Slide 22 text

#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); });

Slide 23

Slide 23 text

#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); });

Slide 24

Slide 24 text

#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); });

Slide 25

Slide 25 text

#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); });

Slide 26

Slide 26 text

#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); });

Slide 27

Slide 27 text

#frontendtesting Some big players • Jasmine • Mocha • Chai • Webdriver.io • QUnit • Unit.js • Sinon

Slide 28

Slide 28 text

#frontendtesting Integration tests check that all the small pieces play well together in the bigger picture.

Slide 29

Slide 29 text

#frontendtesting Unit

Slide 30

Slide 30 text

#frontendtesting Unit Integration

Slide 31

Slide 31 text

#frontendtesting Unit Integration Integration

Slide 32

Slide 32 text

#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'); });

Slide 33

Slide 33 text

#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'); });

Slide 34

Slide 34 text

#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'); });

Slide 35

Slide 35 text

#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'); });

Slide 36

Slide 36 text

#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'); });

Slide 37

Slide 37 text

#frontendtesting Acceptance tests make sure we can accomplish major tasks.

Slide 38

Slide 38 text

#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(); }); }); });

Slide 39

Slide 39 text

#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(); }); }); });

Slide 40

Slide 40 text

#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(); }); }); });

Slide 41

Slide 41 text

#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(); }); }); });

Slide 42

Slide 42 text

#frontendtesting More tools! • jasmine-integration • Karma • Selenium • Nightwatch • Again, many others

Slide 43

Slide 43 text

#frontendtesting Visual Regression Performance Accessibility Unit Integration Acceptance

Slide 44

Slide 44 text

#frontendtesting Visual
 Regression Performance Accessibility Unit Integration Acceptance

Slide 45

Slide 45 text

#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

Slide 46

Slide 46 text

#frontendtesting Visual regression tests check for inconsistencies in the view.

Slide 47

Slide 47 text

#frontendtesting Before After Difference

Slide 48

Slide 48 text

#frontendtesting

Slide 49

Slide 49 text

#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”); });

Slide 50

Slide 50 text

#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”); });

Slide 51

Slide 51 text

#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”); });

Slide 52

Slide 52 text

#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”); });

Slide 53

Slide 53 text

#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”); });

Slide 54

Slide 54 text

#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”); });

Slide 55

Slide 55 text

#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”); });

Slide 56

Slide 56 text

#frontendtesting Visual regression tests full pages vs. components

Slide 57

Slide 57 text

#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”); });

Slide 58

Slide 58 text

#frontendtesting Visual regression tests and that responsive design thing…

Slide 59

Slide 59 text

#frontendtesting phantomcss: { options: { mismatchTolerance: 0.05, screenshots: ‘tests/visual/baselines’, results: ‘tests/visual/results’, viewportSize: [1280, 800] }, src: [‘tests/visual/tests.js’] }

Slide 60

Slide 60 text

#frontendtesting phantomcss: { big_size: { options: { ... viewportSize: [1280, 800] } }, small_size: { options: { ... viewportSize: [480, 320] } } }

Slide 61

Slide 61 text

The workflow, though… #frontendtesting

Slide 62

Slide 62 text

#frontendtesting

Slide 63

Slide 63 text

#frontendtesting

Slide 64

Slide 64 text

#frontendtesting

Slide 65

Slide 65 text

#frontendtesting Visual regression tools • PhantomCSS/PhantomFlow • BackstopJS • Wraith • Webdriver.io • Percy.io

Slide 66

Slide 66 text

#frontendtesting Accessibility tests compare your site against accessibility standards

Slide 67

Slide 67 text

#frontendtesting

Slide 68

Slide 68 text

#frontendtesting Tools I’ve heard of • a11y • use grunt-a11y for build tool automation • Pa11y • ember-a11y-testing • protractor-accessibility-plugin (Angular) • react-a11y

Slide 69

Slide 69 text

#frontendtesting Performance tests keep your project honest about performance

Slide 70

Slide 70 text

#frontendtesting perfbudget: { default: { options: { url: [your site’s URL], budget: { visualComplete: '3000', SpeedIndex: ‘1500’, render: ‘500’, fullyLoaded: ‘3000’, requests: ’25’, bytesInDoc: '100000' } } } }

Slide 71

Slide 71 text

#frontendtesting Tools for performance • grunt-perfbudget • gulp size • perf.js

Slide 72

Slide 72 text

#frontendtesting Monkey testing is something I just read about yesterday morning.

Slide 73

Slide 73 text

#frontendtesting

Slide 74

Slide 74 text

#frontendtesting var horde = gremlins.createHorde(); horde.unleash();

Slide 75

Slide 75 text

#frontendtesting gremlins.createHorde() .gremlin(gremlins.species.formFiller()) .gremlin(gremlins.species.clicker().clickTypes(['click'])) .gremlin(gremlins.species.toucher()) .gremlin(gremlins.species.scroller()) .gremlin(gremlins.species.typer()) .unleash();

Slide 76

Slide 76 text

#frontendtesting gremlin formFiller input 5 in gremlin formFiller input [email protected] in 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

Slide 77

Slide 77 text

#frontendtesting A single library • Gremlins.js

Slide 78

Slide 78 text

What about linting? #frontendtesting

Slide 79

Slide 79 text

#frontendtesting Interested in linting? • CSSLint • JSLint/JSHint • UnCSS • HTMLLint • Framework-specific linting, too!

Slide 80

Slide 80 text

So, you’re saying I should use all these tools, right? #frontendtesting

Slide 81

Slide 81 text

#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?

Slide 82

Slide 82 text

Yes, you can go overboard with tests. #frontendtesting

Slide 83

Slide 83 text

#frontendtesting Visual
 Regression Performance Accessibility Unit Integration Acceptance

Slide 84

Slide 84 text

#frontendtesting Visual
 Regression Performance Accessibility Acceptance

Slide 85

Slide 85 text

#frontendtesting Visual
 Regression Performance Accessibility

Slide 86

Slide 86 text

#frontendtesting Accessibility

Slide 87

Slide 87 text

#frontendtesting Accessibility Unit Integration Acceptance

Slide 88

Slide 88 text

Yes, writing good tests takes practice. #frontendtesting

Slide 89

Slide 89 text

#frontendtesting

Slide 90

Slide 90 text

The Excuse Corner

Slide 91

Slide 91 text

This is more code to write. #frontendtesting

Slide 92

Slide 92 text

This is more code to write, and my code will still have bugs. #frontendtesting

Slide 93

Slide 93 text

I’m working with legacy code, and it’ll be impossible to test old features. #frontendtesting

Slide 94

Slide 94 text

#frontendtesting Build A Test-Conscience Team through education, experimentation, and practice

Slide 95

Slide 95 text

#frontendtesting • Frontend Architecture for Design Systems by Micah Godbolt
 • FrontEndTesting.com Looking for more?

Slide 96

Slide 96 text

Thank you!