{"id":16538657,"url":"https://github.com/kyleect/introduction-to-browser-automation","last_synced_at":"2026-02-03T19:02:49.833Z","repository":{"id":132692272,"uuid":"221551266","full_name":"kyleect/introduction-to-browser-automation","owner":"kyleect","description":null,"archived":false,"fork":false,"pushed_at":"2019-11-15T02:23:12.000Z","size":9,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-24T21:11:28.067Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://bit.ly/2CLUKK6","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kyleect.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-13T21:03:40.000Z","updated_at":"2020-09-14T19:15:49.000Z","dependencies_parsed_at":"2023-12-03T13:15:14.259Z","dependency_job_id":null,"html_url":"https://github.com/kyleect/introduction-to-browser-automation","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kyleect/introduction-to-browser-automation","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleect%2Fintroduction-to-browser-automation","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleect%2Fintroduction-to-browser-automation/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleect%2Fintroduction-to-browser-automation/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleect%2Fintroduction-to-browser-automation/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyleect","download_url":"https://codeload.github.com/kyleect/introduction-to-browser-automation/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyleect%2Fintroduction-to-browser-automation/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29054047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T15:43:47.601Z","status":"ssl_error","status_checked_at":"2026-02-03T15:43:46.709Z","response_time":96,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-11T18:46:17.510Z","updated_at":"2026-02-03T19:02:49.817Z","avatar_url":"https://github.com/kyleect.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction to Browser Automation\n\n## Agenda\n\n### Part One\n\n- What Is Browser Automation?\n- HTML\n- CSS Selectors\n- Setup\n\n### Part Two\n\n- Webdriver\n- Writing Your First Test\n- Page Objects\n- Questions\n\n## Audience\n\nThe target audience for this are testers. It progressively gets more technical but hopefully the concepts are still clear.\n\n## Introduction\n\n- Software and Quality Engineer at Source Allies\n- Director of Education for DAQAA\n- Hobbies\n  - Writing open source test tools/libraries\n  - Synthesizers\n  - Cooking\n\n### Social\n\n- [twitter.com/testingrequired](https://twitter.com/testingrequired)\n- [github.com/testingrequired](https://github.com/testingrequired)\n- [testingrequired.com](https://www.testingrequired.com/)\n\n### Links\n\n- https://testingrequired.github.io/selector-display/\n- https://exampletest.app/\n\n## What Is Browser Automation?\n\nBrowser automation is the scripted control of a web browser. This includes navigation (go to url, refresh page, go back/forward), location elements, performing actions and waiting for application state (elements appear/disappear, title or url changes).\n\nWhile browser automation is generally associated with automated testing it has many other uses such as data scraping, data entry, downloading reports or any other task performed through a web interface.\n\nThis workshop is going to focus on test automation.\n\n## HTML\n\nHTML is a language for structuring a web page or application. This structure is known as the document. The document is a tree comprised of many elements.\n\n### Elements\n\nElements are the building blocks of an HTML document. An element has many properties: `tag`, `attributes`, `children`.\n\n```html\n\u003ctextarea name=\"description\"\u003eType a description\u003c/textarea\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-html-element\n\n#### Tag\n\nAn element's tag defines what kind of element it is. In this case the tag is `textarea`. Different tags will have different attributes and behaviors.\n\n##### Self Closing\n\nSome tags can or must be defined as self closing. This means it has no children and has a slightly different syntax.\n\n```html\n\u003cinput type=\"text\" /\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-html-element-selfclosing\n\n##### Common Tags\n\n[`div`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div), [`p`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p), [`a`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a), [`ul`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul), [`input`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)\n\n#### Classes\n\nElement classes are identifiers that can be applied to one or more elements in the document. Elements can have one or more classes defined at the same time.\n\n```html\n\u003ca href=\"#\" class=\"food vegetable\" id=\"corn\"\u003eCorn\u003c/a\u003e\n\u003ca href=\"#\" class=\"food vegetable\" id=\"carrot\"\u003eCarrot\u003c/a\u003e\n\u003ca href=\"#\" class=\"food fruit\" id=\"apple\"\u003eApple\u003c/a\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-html-class\n\nThe class `vegetable` is defined two of the elements and the `food` class is defined on all three elements.\n\n- https://testingrequired.github.io/selector-display/?share=example-html-class-vegetable\n- https://testingrequired.github.io/selector-display/?share=example-html-class-food\n\n#### Ids\n\nElement ids are identifiers applied to only one element in a document.\n\n```html\n\u003ca href=\"#\" class=\"food vegetable\" id=\"corn\"\u003eCorn\u003c/a\u003e\n\u003ca href=\"#\" class=\"food vegetable\" id=\"carrot\"\u003eCarrot\u003c/a\u003e\n\u003ca href=\"#\" class=\"food fruit\" id=\"apple\"\u003eApple\u003c/a\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-html-id\n\nThe three elements each have a unique id: `corn`, `carrot` and `apple`. All element id's should be unique within the document. This is not a hard and fast rule as most browsers will be perfectly fine with duplicate ids.\n\n```html\n\u003ca href=\"#\" class=\"food vegetable\" id=\"corn\"\u003eCorn\u003c/a\u003e\n\u003ca href=\"#\" class=\"food vegetable\" id=\"carrot\"\u003eCarrot\u003c/a\u003e\n\u003ca href=\"#\" class=\"food fruit\" id=\"apple\"\u003eApple\u003c/a\u003e\n\u003ca href=\"#\" class=\"food fruit\" id=\"apple\"\u003eFuji Apple\u003c/a\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-html-id-duplicate\n\n#### Generated Identifiers\n\nWhen looking for classes and ids you will run across a lot that look nonsensical.\n\n```html\n\u003cdiv class=\"post__PostStyle-sc-1hn3h94-0\"\u003e\u003c/div\u003e\n\u003cdiv class=\"gsluGG\"\u003e\u003c/div\u003e\n```\n\nThese are auto generated by the page/site/application's build process. They are not useful because they are random and will change which will break your automation. They should be ignored.\n\n#### Malformed\n\nHTML is designed to be flexible. Browsers will do their best when parsing HTML and can infer things like missing end tags for elements.\n\n```\n\u003cul\u003e\n  \u003cli\u003eCat\n  \u003cli\u003eDog\n\u003c/ul\u003e\n```\n\n### Tree\n\nElements are structured as a tree where this is a root element with many levels of nesting. Each element being the parent of elements nested inside.\n\n```html\n\u003cform\u003e\n  \u003ctextarea name=\"description\"\u003eType a description\u003c/textarea\u003e\n  \u003cbutton\u003eSubmit\u003c/button\u003e\n\u003c/form\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-html-tree\n\nHere `form` is the parent and `button` is the sibling of the `textarea`.\n\n## CSS Selectors\n\n- https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors\n\n### Types\n\n#### Universal\n\n\u003e Selects all elements. Optionally, it may be restricted to a specific namespace or to all namespaces.\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors\n\nExample: `*`\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-universal\n\n#### Type\n\n\u003e Selects all elements that have the given node name.\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Type_selectors\n\nExample: `p`\n\n```html\n\u003csection\u003e\n  \u003cp\u003eQuick brown fox\u003c/p\u003e\n  \u003cp\u003eJumped over the lazy dog\u003c/p\u003e\n\u003c/section\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-type\n\n#### Class\n\n\u003e Selects all elements that have the given class attribute.\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors\n\nExample: `.description`\n\n```html\n\u003cdiv id=\"car\"\u003e\n  \u003cp class=\"description\"\u003eCar\u003c/p\u003e\n  \u003cimg src=\"car.png\" /\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"truck\"\u003e\n  \u003cp class=\"description\"\u003eTruck\u003c/p\u003e\n  \u003cimg src=\"truck.png\" /\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"bike\"\u003e\n  \u003cp class=\"description\"\u003eBike\u003c/p\u003e\n  \u003cimg src=\"bike.png\" /\u003e\n\u003c/div\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-class\n\n#### Id\n\n\u003e Selects an element based on the value of its id attribute. There should be only one element with a given ID in a document.\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/ID_selectors\n\n```html\n\u003cdiv id=\"car\"\u003e\n  \u003cp class=\"description\"\u003eCar\u003c/p\u003e\n  \u003cimg src=\"car.png\" /\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"truck\"\u003e\n  \u003cp class=\"description\"\u003eTruck\u003c/p\u003e\n  \u003cimg src=\"truck.png\" /\u003e\n\u003c/div\u003e\n\n\u003cdiv id=\"bike\"\u003e\n  \u003cp class=\"description\"\u003eBike\u003c/p\u003e\n  \u003cimg src=\"bike.png\" /\u003e\n\u003c/div\u003e\n```\n\nExample: `#bike`\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-id\n\n#### Attribute\n\n\u003e The CSS attribute selector matches elements based on the presence or value of a given attribute.\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors\n\nExample `input[type=submit]`\n\n```html\n\u003cform\u003e\n  \u003cinput type=\"text\" /\u003e\n  \u003cinput type=\"submit\" value=\"Save\" /\u003e\n\u003c/form\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-attribute\n\n### Grouping\n\n\u003e The CSS selector list (,) selects all the matching nodes.\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Selector_list\n\nExample: `#username, #password, #loginForm button`\n\n```html\n\u003cform action=\"/login\" id=\"loginForm\"\u003e\n  \u003cdiv\u003e\n    \u003clabel for=\"username\"\u003eUsername\u003c/label\u003e\n    \u003cinput type=\"text\" name=\"username\" id=\"username\" /\u003e\n  \u003c/div\u003e\n  \u003cdiv\u003e\n    \u003clabel for=\"password\"\u003ePassword\u003c/label\u003e\n    \u003cinput type=\"text\" name=\"password\" id=\"password\" /\u003e\n  \u003c/div\u003e\n  \u003cbutton\u003eLogin\u003c/button\u003e\n\u003c/form\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-grouping\n\n### Combinators\n\n#### Descendant\n\n\u003e The descendant combinator — typically represented by a single space ( ) character — combines two selectors such that elements matched by the second selector are selected if they have an ancestor (parent, parent's parent, parent's parent's parent, etc) element matching the first selector. Selectors that utilize a descendant combinator are called descendant selectors.\n\n`A B`\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Descendant_combinator\n\nExample: `#parent .grandchild`\n\n```html\n\u003cdiv id=\"parent\"\u003e\n  \u003cdiv class=\"child\"\u003e\n    \u003cdiv class=\"child grandchild\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n`#parent .child` returns all children but we only want direct children. See child combinator below.\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-descendant\n\n#### Child\n\n\u003e The child combinator (\u003e) is placed between two CSS selectors. It matches only those elements matched by the second selector that are the direct children of elements matched by the first.\n\n`A \u003e B`\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Child_combinator\n\nExample: `#parent \u003e .child`\n\n```html\n\u003cdiv id=\"parent\"\u003e\n  \u003cdiv class=\"child\"\u003e\n    \u003cdiv class=\"child grandchild\"\u003e\u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-child\n\n#### General Sibling\n\n\u003e The general sibling combinator (~) separates two selectors and matches the second element only if it follows the first element (though not necessarily immediately), and both are children of the same parent element.\n\n`A ~ B`\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/General_sibling_combinator\n\nExample: `#one ~ #three`\n\n```html\n\u003cul\u003e\n  \u003cli id=\"one\"\u003eOne\u003c/li\u003e\n  \u003cli id=\"two\"\u003eTwo\u003c/li\u003e\n  \u003cli id=\"three\"\u003eThree\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-general-sibling\n\n#### Adjacent Sibling\n\n\u003e The adjacent sibling combinator (+) separates two selectors and matches the second element only if it immediately follows the first element, and both are children of the same parent element.\n\n`A + B`\n\nhttps://developer.mozilla.org/en-US/docs/Web/CSS/Adjacent_sibling_combinator\n\nExample: `#one + #two`\n\n```html\n\u003cul\u003e\n  \u003cli id=\"one\"\u003eOne\u003c/li\u003e\n  \u003cli id=\"two\"\u003eTwo\u003c/li\u003e\n  \u003cli id=\"three\"\u003eThree\u003c/li\u003e\n\u003c/ul\u003e\n```\n\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-adjacent-sibling\n- https://testingrequired.github.io/selector-display/?share=example-css-selector-adjacent-sibling-general\n\n## Setup\n\n### Terminal\n\nThe terminal is needed for some installation and running the tests. Anytime you see a command prefixed with `$` it means to run this command in your terminal.\n\nExample:\n\n`$ npm run test`\n\n### Installation\n\n#### Node\n\nNode is an application that allows us to run javascript outside of a browser. It will be running the test scripts we're writing today.\n\nhttps://nodejs.org/en/\n\nPlease install the LTS version which is currently 12.13.0\n\n#### Git\n\nGit is a version control application. We will just be using it to pull down test suite.\n\nhttps://git-scm.com/download/win\n\n#### Editor\n\nYou can use any editor you like but I would recommend Visual Studio Code because of it's excellent javascript support.\n\n#### Chrome\n\nChrome needs to be installed for the chrome webdriver to connect and the tests to run.\n\n##### Version\n\nThe `chromedriver` version in `package.json` must align with the version of Chrome you have.\n\n#### Aligning With Chrome\n\nYou have two options when matching your Chrome and chromedriver version.\n\n##### Update Chrome\n\nUpdate Chrome to latest and use latest chromedriver is the easiest method. Unfortunately this isn't always possible such as enterprise environments.\n\n##### Install correct chromedriver version\n\nAnother option is to install the correct chromedriver version for your Chrome.\n\n1. Open Chrome\n2. Click on the `...` menu\n3. Click on Help \u003e About Chrome\n4. Locate Chrome major version\n   - Example: `78.0.3904.97`\n   - Major Version: `78`\n5. Find highest `chromedriver` version that matches your Chrome major version\n   - https://www.npmjs.com/package/chromedriver\n   - Example: Chrome `76` chromedriver `76.0.1`\n6. `$ npm install chromedriver@76.0.1` replacing with your chromedriver version\n\n### Obtain Project\n\nYou'll need to obtain the test project to be able to run the test suite.\n\n#### Using git\n\nUsing git is recommended as you'll be able to pull down the latest versions.\n\n1. Open terminal\n2. `$ cd ...` where `...` is the where you want the project to download to\n3. `$ git clone https://github.com/testingrequired/exampletest.app-tests.git`\n4. `$ cd exampletest.app-tests`\n\n#### Download zip\n\nAnother option is to download the project as a zip file.\n\n1. Click on the latest release of the project: https://github.com/testingrequired/exampletest.app-tests/releases\n2. Click `Source code (zip)` and download the zip file\n3. Unzip the project folder where ever you want it to live\n\n### Run Tests\n\n1. `$ cd path/to/the/project`\n   Example: `C:\\Users\\name\\exampletest.app-tests` or `~/exampletest.app-tests`\n2. `$ npm install`\n3. `$ npm run test`\n\n## Webdriver\n\nThe webdriver the application that receives commands from our automation and passes those commands to the browser. Each of the major browser vendors distribute a webdriver.\n\n### Capabilities\n\nWebdrivers allow you to define what \"capabilities\" you want/need to support when running your tests. These capabilities include but not limited to: browser name, browser version \u0026 operating system. This allows for cross browser/operating system testing. There are a lot of complexities around implementing this yourself but services like [saucelabs](https://saucelabs.com/) work out of the box.\n\n### Headless\n\nWhen developing your automation scripts you'll want to watch the browser as the script runs. This gives you real time feedback on what your automation is doing.\n\nWhen your automation scripts are running on a server it's not possible for the browser to launch and be visible. In fact most of the time the browser will error causing the tests to fail instantly.\n\nMany browsers have a feature to run \"headless\" meaning the browser is running but nothing is shown on screen. This will allow it to run on a server and often times will run faster.\n\n### Selenium\n\nIt's more likely you've heard of Selenium versus Webdriver. Selenium was the original implementation. Webdriver was the standard created but created with Selenium compatibility in mind.\n\n### webdriver.io\n\n[webdriver.io](https://webdriver.io) (wdio) is a webdriver library written in javascript. We will be using it for the workshop and\n\n#### Sync vs ASync\n\nwdio supports both [an async and a sync](https://webdriver.io/docs/sync-vs-async.html) mode. The biggest difference between them is with async you must do all of the waiting yourself. With sync mode wdio handles nearly all of the waiting for us. For the workshop we'll be using the sync mode as it's easier to get started with.\n\n## Writing Your First Test\n\nThe test case we are going to write is one of the most common you'll come across: logging in.\n\n### Test Case\n\nAll test automation should start from a test case. What are the conditions, actions and assertions need to test a piece of functionality. For logging in it might be:\n\n```\nGiven an existing user of testUser\nWhen I enter the correct username and password\nThen I'm logged in as testUser\n```\n\n### Identifying/Finding Elements\n\nElements to identify\n\n1. On the main page: Login Link\n2. On the login page: Login form, username \u0026 password inputs and the login button\n\n### Perform Actions\n\n1. Click login link\n2. Fill username and password\n3. Click login button\n\n### Wait And Assert State\n\n1. Wait for login form to load\n2. Wait for user page to load\n\n## Page Objects\n\n### What\n\nA way to tie elements and actions together as a single UI component: e.g. login form, navigation menu.\n\n### Why\n\nThe two main benefits to page objects are abstraction and reusability.\n\n#### Abstraction\n\nPage objects abstract how the UI actually works by exposing properties (elements) and methods (actions) to call. As long as the application is in the correct state then the page object will just work.\n\n#### Reusability\n\nThe abstraction page objects provide also makes them reusable. Got a nav menu that displays on all pages? Use a single page object. Got a rating form thats on several pages? Single page object.\n\n### Boundaries\n\nPage objects should have some concept of the elements its responsible for. The outer boundary of the page object is often its root element. Where every other element it's responsible for is a child of that root element.\n\nA good example of this would be an application with a login form. The root element is likely the `form` element while other elements the page object is responsible for are the username, password inputs and submit button. Any element outside the root element should not be interacted with using that page object.\n\n### Element Getters\n\nElement's should be defined on page objects as either methods or getters. This ensures every time you reference an element on the page you have a fresh reference to it. Anytime the page reloads or elements disappear/reappear on the page then the previous reference you had to it becomes stale. It's always better to query for elements each and every time.\n\n### Action Methods\n\nActions should be defined on page objects as methods. Ideally the methods should be as atomic as possible while composing those in to larger less generic actions.\n\nAtomic Actions:\n\n- Fill username\n- Fill password\n- Click login button\n\nComposed Actions:\n\n- Login in as user\n\n### Advanced Design Patterns\n\nThis [blog post](https://www.testingrequired.com/blog/modern-page-objects) goes in to more advanced page object design patterns: root element, applying page object to element query results, proxying method calls to the root element. This pattern allows you to compose page objects together, override element behaviors, automatically scope element queries and more.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyleect%2Fintroduction-to-browser-automation","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyleect%2Fintroduction-to-browser-automation","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyleect%2Fintroduction-to-browser-automation/lists"}