{"id":20494137,"url":"https://github.com/jaystack/e2e_box","last_synced_at":"2025-04-13T17:21:30.110Z","repository":{"id":146253004,"uuid":"96249213","full_name":"jaystack/e2e_box","owner":"jaystack","description":"An implementation of a robust selenium e2e setup","archived":false,"fork":false,"pushed_at":"2017-09-15T16:15:30.000Z","size":641,"stargazers_count":9,"open_issues_count":0,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-03-27T08:11:20.134Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/jaystack.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":"2017-07-04T19:55:45.000Z","updated_at":"2021-07-04T14:33:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"3bed601b-f04b-4a67-baef-8ebcd20f4ca9","html_url":"https://github.com/jaystack/e2e_box","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaystack%2Fe2e_box","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaystack%2Fe2e_box/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaystack%2Fe2e_box/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaystack%2Fe2e_box/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaystack","download_url":"https://codeload.github.com/jaystack/e2e_box/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248750851,"owners_count":21155795,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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-11-15T17:38:23.163Z","updated_at":"2025-04-13T17:21:30.097Z","avatar_url":"https://github.com/jaystack.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# E2E testing with cucumber, selenium-webdriver and docker-compose\n\n## Quick info if you don't want to read much\n\nPull the e2e_box repository\n\n## Running in local DEV mode\n\nExecute tests found in e2e_box/tests/features\n```\ne2e\n❯ docker-compose run tests\n```\n\nStart system without running tests\n```\ne2e\n❯ docker-compose up -d web\n```\n\n*Features*\n- access website being tested on [localhost:5000](http://localhost:5000/)\n- access website backend REST API on [localhost:5001](http://localhost:5001/)\n- access Selenium vnc viewerport on [localhost:5900](http://localhost:5900/)\n- live edits and live-reload for website: open `e2e_box/web/src`\n- live edits and live-reload for api: open `e2e_box/api/src`\n- live edits and fast test run for test code using Cucumber.js\n- zero complication with selenium setup\n\n## Running in CI mode\n```\ndocker-compose -f docker-compose.yml run tests \u0026\u0026 \\\n docker-compose -f docker-compose.yml down\n```\n\n# E2E testing with cucumber, selenium and docker-compose\n\nBrowser based E2E tests are awesome. You just create a couple of lines of test code and you can cover several thousand lines of application logic and tons of css rule.\n\nBrowser based E2E tests are also a pain. They are usually felt fragile, complicated to develop and maintain, sometimes non-deterministic with a lot of false positive runs, and prone to break from a simple change in the markup.\n\nIs all lost then? I hope not, so I collected what was learned from some recent experience - and share some tips on building an E2E environment that is less prone to the above issues and is more likely to hold on the longer run - and also fun to use.\n\n## The tools used in this demo\n\n#### Selenium WebDriver with Chrome\nSelenium (webdriver) is like the [Khumbu Icefall](https://en.wikipedia.org/wiki/Khumbu_Icefall) when it comes to summitting the Everest from the South Col - there's no way around it. However with some simple tricks we can achieve decent reliability...\n\n#### ~~Selenium WebDriver with Chrome Headless~~\nSorry guys, it's just not there yet - but very very close. Read  more at the end.\n\n#### docker and docker-compose\nDocker and docker-compose provides our sandbox that can spin up in any environments that has docker installed - letting us run the very same E2E test system in local development time or during the CI/CD pipeline.\n\n#### selenium-webdriver library\nUsing the lowest level driver (okay, almost) provides a better understanding of the overall test system - and is `one less api and documentation` to know about.\n\n#### gherkin style tests with Cucumber.js\nIt could be really anything, like mocha - in our original case tests were created `by the bussiness` and they already love the gherkin syntax. Done in BDD tyle or not - the gherkin syntax, and keeping test specificaions separate from implementation code has some nice benefits. Non developers being able to participate in their creation, and data driven tests are just two it.\n\n## The big picture\n\nInstead of relying on shared, permanent services, like a shared Selenium server in test env, or a development webserver, we will have all our system components captured as docker images, and started as dedicated containers - that exist just for the lifetime of a single test run. Each time we execute the tests, our required services will start afresh in perfect new condition - giving the highest chance for a predictable execution result. Also, instead of running the E2E test code using the webserver process, which is a common practice, we will run it in its dedicated worker process and container.\n\n\u003cimg width=\"50%\" align=\"right\" src=\"https://raw.githubusercontent.com/jaystack/e2e_box/master/content/compose2.png\" /\u003e|\n\nThat's a lot of containers to deal with, you might say, more so, if you'll add some more tiers like REST api service and a mongodb - for a more realistic application. The good news is: you'll don't need to deal with those containers. Docker-compose to our help: all of the above will be encapsulated in a \u003cb\u003edocker-compose project\u003c/b\u003e - so we can use simple docker-compose commands to start/run/stop the whole test system, or just parts. The \u003cb\u003edefault docker network\u003c/b\u003e provided by docker-compose also serves as a sandbox, that wraps the set of containers created for each test runs. This lets the different service components to always know about each other without extra configuration effort. For example the test application can always access Selenium as `http://selenium` while the Selenium service can always access the website as `http://web`  no matter where we are running the tests.\n\n\n## The implementation\n\nA fully built version of the test system can be found at https://github.com/jaystack/e2e_box.\n\n### The test application\nSource: https://github.com/jaystack/e2e_box/tree/master/tests\n\nThis document isn't aiming to be a detailed material on the `gherkin syntax`, or the `cucumber.js` library, or the means to unleash your BDD super idenity. It just tries to show some interesting basics so that you'll wanna dig deeper on your own.\n\nCucumber.js has a unique way for providing the otherwise usual functionality of a test framework: its functional mainly with a pinch of classes and instances. Sounds doggy but its actually a very nice balance.\n\nTests are specified through a list of features definitions, which are stand alone text files with one or more scenarios in them that are plain English sentences with some basic semantic rules in them. They must have a `.feature` file extension.\n**tests/features/welcome.feture**\n```gherkin\nFeature: Website main page\n\nScenario: Visitors are welcomed\n Given a website to accept visitors\n When  I open the main page\n Then I see a welcome message saying \"Welcome to React\"\n```\n\n`Given`, `When`, `Then` are the guys you probably know as Arrange, Act, Assert from your previous experiences. You can have multiple of them and you can change them anyplace to `And` for better readability.\n\nTest implementation is given as a set of match handlers - against the specification text.\n**tests/features/step_definitions/steps.js**\n```javascript\nconst { defineSupportCode } = require('cucumber');\n\ndefineSupportCode(function ({ Given, When, Then }) {\n    Given(/a website to accept visitors/, async function() {\n        // the `this` holds the actual World instance - read below\n        this.driver = await this.createSeleniumDriver();\n    })\n\n    When(/I open the main page/, async function() {\n        await this.driver.get(this.webUrl);\n    })\n\n    Then(/I see a welcome message saying \"(.*)\"/, async function(expected) {\n        const driver = this.driver;\n        const welcomeBox = await locateByCss(driver, '[data-welcome]');\n        const content = await welcomeBox.getText();\n        assert.equal(content, expected);\n    });\n```\nNotice the extensive use of `this`. Cucumber tests are stateful: Scenarios, when executed, get a dedicated World instance that operates as their state.\n\n**a basic World class**\n```javascript\nconst { defineSupportCode } = require('cucumber');\n\nclass World {\n  constructor({ parameters }) {\n\n  }\n}\n\ndefineSupportCode(({setWorldConstructor} ) =\u003e {\n  setWorldConstructor(World);\n});\n```\n\nScenario test code is free to alter the `world instance` during the test run, and since the order of the steps matter we can leverage this for some cool/unpure/side effecting ways to implement our tests.\n\nThe `World class` is also the place to create shared code or lower level APIs - that step definitions later can simply access from `this`. Wrapping the Selenium Webdriver connect/disconnect logic, or a backend mongodb connection are some examples. The `constructor` of the `World class` receives the startup parameters (if any) in javascript object.\n\n**tests/features/support/World.js**\n```javascript\nclass World {\n  constructor({ parameters }) {\n    const {\n      seleniumUrl = 'http://selenium:4444/wd/hub',\n      apiUrl = 'http://api:5001',\n      webUrl = 'http://web:5000'\n    } = parameters;\n    this.cleanUpTasks = [];\n    Object.assign(this, { seleniumUrl, apiUrl, webUrl });\n  }\n\n  get apiClient() {\n    const { apiUrl } = this;\n    return {\n      post: createPostMethod(apiUrl),\n      get: createGetMethod(apiUrl)\n    }\n  }\n\n  async createSeleniumDriver() {\n    const driver = await new seleniumWebdriver.Builder()\n      .forBrowser('chrome')\n      .usingServer(this.seleniumUrl)\n      .build();\n    this.driver = driver;\n    this.cleanUpTasks.push(async () =\u003e await driver.quit());\n    return driver;\n  }\n\n  /* {  some code is omitted for brevity } */\n}\n```\nThe above world implementation takes the service addresses as parameter - and if no parameter is passed, it falls back on safe default values. Also it provides the `createSeleniumDriver` and the `apiClient` world instance members for test steps to easily access test infrastructure.\n\nThe `gherking syntax` lets us embed data into the test specification so that test code can be data driven. Here is a lengthy example:\n\n**tests/features/cart.feature**\n```gherkin\nFeature: Product list\n\nBackground:\n  Given a website to accept visitors\n  Given the product database contains the following items\n  |name          |price  |\n  |Picalilly     |4      |\n  |Ribeye steak  |8      |\n  |Sirloin steak |6      |\n  |Rocket salad  |2      |\n\nScenario: Adding items to the cart multiple times\n  Given I navigate to the product page\n  Then I see \"4\" items in the product list\n  When I put the \"Picalilly\" product in my cart\n  Then I see \"1\" items in my cart\n   And the total cart value is \"£ 4\"\n  When I put the \"Picalilly\" product in my cart\n  Then I see \"2\" items in my cart\n   And the total cart value is \"£ 8\"\n  When I put the \"Sirloin steak\" product in my cart\n  Then I see \"3\" items in my cart\n   And the total cart value is \"£ 14\"\n```\n\n\nCUCUMBER INTRO COMES HERE\n\n### The docker-compose setup\n\nDOCKER-COMPOSE FILE AND USAGE DETAILED\n\n\n### The test subject #1: a create-react-app application\nSource: https://github.com/jaystack/e2e_box/tree/master/web\n\nIt is  irrelevant what technlogy is was to create the solution, as long as one can build a docker container to host and run it. It is a particularly simple process for a nodejs/React app (but not too complicated for Java or .NET either) - all it takes is a Dockerfile, that we place in the website root for simplicity sake. A very simple version of it, that expects a `build` and a `service` package script to do the appliction specific steps would look like this:\n\n**web/Dockerfile**\n```Dockerfile\nFROM node:6\nWORKDIR /app\nADD package-lock.json .\nADD package.json .\nRUN npm i\nADD . .\nRUN npm run build\n\nENTRYPOINT [\"npm\"]\nCMD [\"run\",\"service\"]\n\n```\nNote that the package*.json files are added to the image first and source files only after `RUN npm i`. This way installing packages will fulfilled from local docker cache the next time we rebuild this image - assuming the package files haven't changed.\n\nAlso note that we build the web application inside the container with `npm run build`. This way the whole build process is also encapsulated.\n\n\nLater on the docker-compose utility will use this file to build an image for us.\n####\n\n\n#### Read stuff\n\nhttp://docs.behat.org/en/v2.5/guides/1.gherkin.html#backgrounds\nhttp://docs.behat.org/en/v2.5/guides/1.gherkin.html\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaystack%2Fe2e_box","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaystack%2Fe2e_box","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaystack%2Fe2e_box/lists"}