diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml new file mode 100644 index 00000000..ca8d109d --- /dev/null +++ b/.github/workflows/cypress.yml @@ -0,0 +1,37 @@ +name: Cypress + +on: + push: + branches: [main] + pull_request: + types: [synchronize, opened, reopened] + +jobs: + cnsi_portal_e2e_test: + runs-on: ubuntu-latest + strategy: + matrix: + node_version: [14.19.0] + container: cypress/base:${{ matrix.node_version }} + steps: + - uses: actions/checkout@v3 + + - name: Install dependencies + run: | + cd src/frontend + if [ -e yarn.lock ]; then + yarn install --frozen-lockfile + elif [ -e package-lock.json ]; then + npm ci + else + npm i + fi + + - name: Cpyress run + uses: cypress-io/github-action@v5 + with: + install: false + working-directory: src/frontend + start: npm run start + wait-on: 'http://localhost:4004' + wait-on-timeout: 120 \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1acb35aa..25b68af9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,3 +47,29 @@ jobs: fail_ci_if_error: true # optional (default = false) verbose: true # optional (default = false) + UI_UT: + env: + UI_UT: true + runs-on: + #- self-hosted + - ubuntu-latest + timeout-minutes: 100 + steps: + - uses: actions/setup-node@v3 + with: + node-version: '18' + - uses: actions/checkout@v3 + with: + path: ${{ github.repository }} + - name: script + run: | + pwd + cd ${{ github.repository }}/src + cd frontend + bash ./test/ui_unit.sh + df -h + - name: Codecov For UI + uses: codecov/codecov-action@v3 + with: + file: ${{ github.repository }}/src/frontend/coverage/icov.info + flags: unittests \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index efe49a24..54696f86 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,13 +2,27 @@ We welcome contributions from the community and first want to thank you for taking the time to contribute! -Please familiarize yourself with the [Code of Conduct](https://github.com/vmware/.github/blob/main/CODE_OF_CONDUCT.md) before contributing. - -Before you start working with cloud-native-security-inspector, please read and sign our Contributor License Agreement [CLA](https://cla.vmware.com/cla/1/preview). If you wish to contribute code and you have not signed our contributor license agreement (CLA), our bot will prompt you to do so when you open a Pull Request. For any questions about the CLA process, please refer to our [FAQ]([https://cla.vmware.com/faq](https://cla.vmware.com/faq)). +**Table of Contents** + +* [Code of Conduct](#code-of-conduct) +* [Ways to Contribute](#ways-to-contribute) +* [Sign a Contributor License](#sign-a-contributor-license) +* [Getting Started](#getting-started) +* [Contribution Flow](#contribution-flow) + * [Staying In Sync With Upstream](#staying-in-sync-with-upstream) + * [Updating pull requests](#updating-pull-requests) + * [Formatting Commit Messages](#formatting-commit-messages) + * [Pull Request Checklist](#pull-request-checklist) +* [Reporting Bugs and Creating Issues](#reporting-bugs-and-creating-issues) +* [Ask for Help](#ask-for-help) + +## Code of Conduct +Please familiarize yourself with the [Code of Conduct](https://github.com/vmware/.github/blob/main/CODE_OF_CONDUCT.md) +before contributing. ## Ways to contribute -We welcome many different types of contributions and not all of them need a Pull request. Contributions may include: +We welcome many types of contributions and not all of them need a Pull request. Contributions may include: * New features and proposals * Documentation @@ -20,9 +34,33 @@ We welcome many different types of contributions and not all of them need a Pull ## Getting started -Please refer to README to learn more about how to build the project from source and run. +Please refer to [README](README.md) to learn more about how to build the project from source code and run. + +All todo items, are recorded on the issue [page](https://github.com/vmware-tanzu/cloud-native-security-inspector/issues). +You can pick one of them by add comments below, then a maintainer can assign the feature to you. + +After picking the issue, before any code change, a design doc should be proposed first. +Check the [template](docs/design/template.md) to see how to write a design doc. + +After the design doc is approved by two maintainers, you can start the implementation process. +It would be nice if you can comment in the issue assigned to you about when is the feature expected to be finished, +so that the maintainers can schedule the issue in the appropriate release in the future. + +## Sign a contributor license +If you would like to contribute code to cloud-native-security-inspector, you must read and sign our +Contributor License Agreement [CLA](https://cla.vmware.com/cla/1/preview). If you wish to contribute +code and you have not signed our contributor license agreement (CLA), our bot will prompt you to do +so when you open a Pull Request. For any questions about the CLA process, please refer to our +[FAQ]([https://cla.vmware.com/faq](https://cla.vmware.com/faq)). +When you click the link for signing the CLA, please sign the individual CLA. + + + +**Important: Make sure you provide all the information we ask you to provide, e.g., fill in each blank in below form.** + + ## Contribution Flow @@ -35,7 +73,8 @@ This is a rough outline of what a contributor's workflow looks like: * Push your changes to the topic branch in your fork * Create a pull request containing that commit -We follow the GitHub workflow and you can find more details on the [GitHub flow documentation](https://docs.github.com/en/get-started/quickstart/github-flow). +We follow the GitHub workflow and you can find more details on the +[GitHub flow documentation](https://docs.github.com/en/get-started/quickstart/github-flow). Example: ```shell @@ -82,31 +121,23 @@ Be sure to include any related GitHub issue references in the commit message. S [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and commits. - - - ### Pull Request Checklist Before submitting your pull request, we advise you to use the following: -1. Check if your code changes will pass both code linting checks and unit tests. +1. Make sure you code change brings no regression, e.g., you change must pass every github workflow item triggerred by your PR. 2. Ensure your commit messages are descriptive. We follow the conventions on [How to Write a Git Commit Message](http://chris.beams.io/posts/git-commit/). Be sure to include any related GitHub issue references in the commit message. See [GFM syntax](https://guides.github.com/features/mastering-markdown/#GitHub-flavored-markdown) for referencing issues and commits. 3. Check the commits and commits messages and ensure they are free from typos. +4. Make sure you have involved new tests that can verify that your change does can solve the problem. ## Reporting Bugs and Creating Issues For specifics on what to include in your report, please follow the guidelines in the issue and pull request templates when available. When opening a new issue, try to roughly follow the commit message format conventions above. - - ## Ask for Help The best way to reach us with a question when contributing is to ask on: * The original GitHub issue -* The developer mailing list - - - - +* The developer mailing list: narrows@vmware.com diff --git a/README.md b/README.md index 6c280ae5..c9ad5c7a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Cloud Native Security Inspector (Project Narrows) [![CodeQL](https://github.com/vmware-tanzu/cloud-native-security-inspector/actions/workflows/codeql.yml/badge.svg)](https://github.com/vmware-tanzu/cloud-native-security-inspector/actions/workflows/codeql.yml) +[![Cypress](https://github.com/vmware-tanzu/cloud-native-security-inspector/actions/workflows/cypress.yml/badge.svg)](https://github.com/vmware-tanzu/cloud-native-security-inspector/actions/workflows/cypress.yml) Cloud Native Security Inspector is an open source cloud native runtime security tool. It allows end users to assess diff --git a/docs/PORTAL-TEST.md b/docs/PORTAL-TEST.md new file mode 100644 index 00000000..d250145a --- /dev/null +++ b/docs/PORTAL-TEST.md @@ -0,0 +1,161 @@ +# Cloud Native Security Inspector Front-End Developer Testing Guide +This is a project based on Jasmine and Karma for unit testing and Cypress e2e for testing the front end. + +Unit test +============ + +1. [Jasmine](https://jasmine.github.io/tutorials/your_first_suite) learns to use official documents. + +2. [Angular Testing](https://angular.io/guide/testing) Learning Documentation。 + +3. When modifying components or creating new ones, synchronous unit testing of newly added code is required. The test code can be carried out according to the [policy component](../src/frontend/src/app/view/policy/policy.component.spec.ts) below. + +```ts +describe('PolicyComponent', () => { + // The test content needs to be written into the modified function + let component: PolicyComponent; + let fixture: ComponentFixture; + let policyService: PolicyService + // Define the results that the test needs to return in advance + const policyServiceStub = { + getInspectionpolicies() { + return of({apiVersion: '', + items: [], + kind: '', + metadata: { + continue: '', + remainingItemCount: 0, + resourceVersion: '', + selfLink: '' + }}); + }, + } + // Inject the involved classes into the test unit, and instantiate + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PolicyComponent ], + imports: [ShardTestModule, RouterTestingModule], + providers: [PolicyService], + schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // Add the corresponding test code according to the newly added code + describe('getInspectionpolicies()', () => { + it('get inspectionpolicies', () => { + component.modifyPolicy() + component.deleteModalHandler('test') + component.deletePolicy() + + }); + + it('getInspectionpolicies', fakeAsync(() => { + spyOn(policyService, 'getInspectionpolicies').and.returnValue( + policyServiceStub.getInspectionpolicies() + ); + component.getInspectionpolicies(); + fixture.detectChanges(); + expect(policyService.getInspectionpolicies).toHaveBeenCalled(); + tick(1500); + expect(policyService.getInspectionpolicies); + })); + }); +}); + +``` +4. After the unit test code is completed, open the src/frontend terminal under the Cloud Native Security Inspector project directory +```bash +$ cd src/frontend + +$ npm run test + +``` +5. You can view the test coverage of each file by modifying [karma.conf.js](../src/frontend/karma.conf.js) as follows。 + +```js + coverageReporter: { + type: 'html', // view via browser + ... + }, + ... + singleRun: false, // Set singleRun to false to view via browser + +``` + +6. Go to src/frontend/coverage/lcov-report under the Cloud Native Security Inspector project directory through Finder, and open index.html through a browser to view the test coverage of each file. +![avatar](./pictures/portal-coverage.png) + + +e2e test +============ +1. [Cypress](https://docs.cypress.io/api/commands/and) learning and using official documents + +2. When modifying or creating a new UI page, it is necessary to perform synchronous automated testing on the newly added code. The test code can follow the [policy.cy.ts](../src/frontend/cypress/e2e/policy.cy.ts) below. + +```ts +describe('Setting Test', () => { + beforeEach(() => { + Cypress.on('uncaught:exception', (err, runnable) => { + // returning false here prevents Cypress from + // failing the test + return false + }) + // mock API + cy.intercept('GET', environment.api.goharbor + '/inspectionpolicies', { fixture: 'policy.json' }) + cy.intercept('GET', '/proxy/apis/goharbor.goharbor.io/v1alpha1/settings', { fixture: 'settings.json' }) + cy.intercept('POST', environment.api.goharbor + '/inspectionpolicies', { + statusCode: 201, + body: { + msg: 'created sussessful!', + }, + }) + // The page route ready to load + cy.visit('http://localhost:4004/policy') + }) + + + it('new policy', () => { + // create policy + cy.intercept('GET', environment.api.goharbor + '/inspectionpolicies', { fixture: 'policy-list.json' }) + // return policy create + cy.visit('http://localhost:4004/modify-policy/create') + // Populate page data + cy.get('[data-cy=name]').type('policy-test', {force: true}) + cy.get('[data-cy=namespace]').type('cronjobs', {force: true}) + cy.get('[data-cy=imagePullPolicy]').select('Always') + cy.get('[data-cy=settingsName]').select('sample-setting') + + + cy.get('[data-cy=next-one]').click() + cy.get('[data-cy=policySettingAddItem]').click() + + cy.get('[data-cy=key]').type('default', {force: true}) + cy.get('[data-cy=value]').type('true', {force: true}) + + cy.get('[data-cy=next-two]').click() + + // create policy + cy.get('[data-cy=created]').click() + + }) + +}) + +``` + +3. After the e2e test code is completed, open the src/frontend terminal under the Cloud Native Security Inspector project directory +```bash +$ cd src/frontend + +# View the test results directly on the terminal +$ npm run cypress:run +# or +# View the test process and test results through the browser +$ npm run cypress:open + +``` \ No newline at end of file diff --git a/docs/contributing-pictures/cla-information.png b/docs/contributing-pictures/cla-information.png new file mode 100644 index 00000000..3191d6c6 Binary files /dev/null and b/docs/contributing-pictures/cla-information.png differ diff --git a/docs/contributing-pictures/sign-cla.png b/docs/contributing-pictures/sign-cla.png new file mode 100644 index 00000000..11ef973f Binary files /dev/null and b/docs/contributing-pictures/sign-cla.png differ diff --git a/docs/design/architecture-refactor.md b/docs/design/architecture-refactor.md new file mode 100644 index 00000000..07e42355 --- /dev/null +++ b/docs/design/architecture-refactor.md @@ -0,0 +1,109 @@ +# Project Narrows Architecture Refactoring +## Abstract +This doc will cover the current design and redesign of Project Narrows to improve readability and maintainability of +source code as well as the overall structure and functionality. Hopefully, it makes code easier to expand upon and +add new features. + +## Background +Today, Project Narrows consists of the following 3 internal components and a external datastore. +### Internal components: +#### 1.[Controller Manager](https://github.com/vmware-tanzu/cloud-native-security-inspector/tree/main/src/controllers): +It reconciles the CR of CNSI and runs as the Deployment in Kubernetes. +#### 2.[Portal](https://github.com/vmware-tanzu/cloud-native-security-inspector/tree/main/src/frontend): +It is written by AngularJS and followed [Clarity](https://clarity.design/) style. The portal talks to Kubernetes API +server and fetches data from OpenSearch. +#### 3.[Scanners](https://github.com/vmware-tanzu/cloud-native-security-inspector/tree/main/src/pkg/inspection): +Currently there are 3 kinds of scanners(image scanner, kube-bench scanner and risk scanner) running as CronJob resource in Kubernetes. +Scanners are triggered periodically and scheduled to different Kubernetes nodes. +### External datastore: +#### OpenSearch or ElasticSearch +A OpenSearch or ElasticSearch instance can be set in policy of Project Narrows. The reports generated by the scanning job can +be exported to the external datastore instance. At the same time, the portal can fetch the data in datastore and show it in portal. +Currently, only OpenSearch and ElasticSearch are supported in Project Narrows. + +## Glossary +* Management cluster: +A Kubernetes cluster that manages the lifecycle of Workload Clusters. A Management Cluster is also where one or more +providers run, and where resources such as Machines are stored. +* Workload cluster: +A Kubernetes cluster whose lifecycle is managed by a Management Cluster. + +## Goals +* To define common operations, provide a default implementation, and provide the ability to swap out implementations for alternative ones. +* To work in different environments, both on-premises and in the cloud. +* To reuse and integrate existing ecosystem components rather than duplicating their functionality (e.g. Falco, Kube-bench, Harbor). + +## Non-goals +* Define a new set of security guidelines or best practices. +* Implement the whole security stack + +## High-Level Design +![Narrows for single cluster](https://github.com/4everming/cloud-native-security-inspector/blob/doc/commit-the-design-doc-for-new-architecture-of-Narrows/docs/pictures/architecture-refactor-1.png) +
single cluster
+ +![Narrows for multi-cluster](https://github.com/4everming/cloud-native-security-inspector/blob/doc/commit-the-design-doc-for-new-architecture-of-Narrows/docs/pictures/architecture-refactor-multi-cluster.png) +
multi-cluster
+ +## Detailed Design +There are several components as the picture showing above which can be divided into 3 categories: +### 1. Components on the control plane node: +* **Manager(Portal):** +A component that allows user to set up the configurations and security policies of Project Narrows, and acquire the scanning reports +* **Resource Collector:** +Talk to Kubernetes API server to collect the Kubernetes workloads related info. In addition to that, +it also talks to external image registries such as Harbor and DevOps platform such as GitLab to collect the security data. +* **Database:** +It is a relational database to store the data collected from **Resource Collector** and **Data Collector** +* **Scanner** +It is a component to scan the resource based on the data which has already been gathered to databases according to +the polices defined by user. After that it will generate the scanning reports. It can be triggered manually or periodically. +* **Data Exporter** +It is a component to export the scanning reports generated by **Scanner** to other places outside Project Narrows so that +other system can consume the data easily and make capabilities of Project Narrows can be leveraged by others. +* **Rule Engine Connector** +It is a component to connect to the policy engine such as OPA(Open Policy Agent). +### 2. Components on each worker node: +* **Data Collector** +Data Collector will collect all the security related data from Kubernetes worker node and sync the data to database on +control plane node, including the security data of worker node and containers on this worker node. +* **Action Enforcer** +It will receive the action items from components on control plane node. +### 3. External components: +* **Image registries** +It is the place to manage the image artifacts and scan the images to produce security data as well. +* **DevOps platforms** +It is the place to track the source code and generate the scanning reports based on the code. +## Roadmap +### Phase 0 +This will introduce a new runtime security tool "Falco" to Project Narrows with the refactored architecture. In order to make it +easy and quick for the first deliverables, we will only implement the necessary parts and keep other parts of the +project the same as before. So you should expect the Cronjob resource created by Narrows are still existing in Kubernetes +and some Daemonsets are introduced. + +### Phase 1 +In this phase, we plan to remove all the Cronjob resources created by Project Narrows and move all the functionalities to +the agent in Daemonsets. After that, we should have a clear and flexible architecture on the node agent side to adapt +new node/container security components, and make the scanning jobs decoupled with Kubernetes scheduler as well. + +### Phase 2 +This will implement the rule engine connector and data exporter parts to make it easy to be integrated with other solutions. + +### Phase 3 +In this phase, we plan to investigate the possibilities to integrate with DevOps platform such as GitLab etc. + +### Phase 4 +In this phase, we will try to expand the Narrows capabilities to multi-cluster, so that users will have a central place +to know clearly about the security trends of all their clusters. +## Test Plan +Unit tests and E2E tests should be included and integrated to CI system. +Unit tests cover individual functions within the project. These tests +should be independent and fast, and should cover all the possible paths in the tested function, including negative and +failure cases. It should mock all other objects other than the one that is currently being tested, and should test that +the appropriate fields are passed as output or as function calls to other parts of the code base. +E2E tests should cover the users' basic flow of Project Narrows and "sigs.k8s.io/e2e-framework" is introduced to set up +env for E2E tests. +## References +[Open Policy Agent](https://www.openpolicyagent.org/docs/latest/) +[Kyverno](https://kyverno.io/) +[Cluster API](https://cluster-api.sigs.k8s.io/introduction.html) + diff --git a/docs/design/template.md b/docs/design/template.md new file mode 100644 index 00000000..94b47fd1 --- /dev/null +++ b/docs/design/template.md @@ -0,0 +1,16 @@ +# Design doc template + +## Abstract +Tell what you are going to do by as few as possible words. + +## Background +Define the problem, and introduce the background. + +## Goals +Tell the goals we are going to achieve by this design. + +## High-Level Design +Describe the high-level design. + +## Detailed Design +Elaborate more about your design. diff --git a/docs/pictures/architecture-refactor-1.png b/docs/pictures/architecture-refactor-1.png new file mode 100644 index 00000000..3dd5bc70 Binary files /dev/null and b/docs/pictures/architecture-refactor-1.png differ diff --git a/docs/pictures/architecture-refactor-multi-cluster.png b/docs/pictures/architecture-refactor-multi-cluster.png new file mode 100644 index 00000000..fea15bb3 Binary files /dev/null and b/docs/pictures/architecture-refactor-multi-cluster.png differ diff --git a/docs/pictures/portal-coverage.png b/docs/pictures/portal-coverage.png new file mode 100644 index 00000000..5653388a Binary files /dev/null and b/docs/pictures/portal-coverage.png differ diff --git a/src/Makefile b/src/Makefile index 54c9889a..0c111c64 100644 --- a/src/Makefile +++ b/src/Makefile @@ -90,19 +90,19 @@ run: manifests generate fmt vet ## Run a controller from your host. docker-build-all: docker-build-manager docker-build-inspector docker-build-kubebench docker-build-portal docker-build-risk docker-build-manager: test build-manager ## Build docker image with the manager. - $(DOCKERCMD) build -t ${IMG_MANAGER} . + $(DOCKERCMD) buildx build -t ${IMG_MANAGER} . docker-build-inspector: build-inspector ## Build docker image with inspector cmd. - $(DOCKERCMD) build -t ${IMG_CMD_INSPECTOR} -f Dockerfile.inspection . + $(DOCKERCMD) buildx build -t ${IMG_CMD_INSPECTOR} -f Dockerfile.inspection . docker-build-kubebench: build-kubebench ## Build docker image with kubebench cmd. - $(DOCKERCMD) build -t ${IMG_CMD_KUBEBENCH} -f Dockerfile.kubebench . + $(DOCKERCMD) buildx build -t ${IMG_CMD_KUBEBENCH} -f Dockerfile.kubebench . docker-build-portal: - $(DOCKERCMD) build -t ${PORTAl} -f Dockerfile.portal . + $(DOCKERCMD) buildx build -t ${PORTAl} -f Dockerfile.portal . docker-build-risk: build-risk ## Build docker image with risk cmd. - $(DOCKERCMD) build -t ${RISK} -f Dockerfile.riskmanager . + $(DOCKERCMD) buildx build -t ${RISK} -f Dockerfile.riskmanager . docker-push: ## Push docker images with the manager. $(DOCKERCMD) push ${IMG_MANAGER} diff --git a/src/frontend/.stylelintrc.json b/src/frontend/.stylelintrc.json index 08b8343f..dcfde459 100644 --- a/src/frontend/.stylelintrc.json +++ b/src/frontend/.stylelintrc.json @@ -9,6 +9,7 @@ "no-descending-specificity": null, "function-no-unknown": null, "hue-degree-notation": null, - "no-duplicate-selectors": null + "no-duplicate-selectors": null, + "at-rule-no-unknown": null } } \ No newline at end of file diff --git a/src/frontend/angular.json b/src/frontend/angular.json index 703faaa7..019c1f64 100644 --- a/src/frontend/angular.json +++ b/src/frontend/angular.json @@ -118,6 +118,51 @@ "src/**/*.html" ] } + }, + "cypress-run": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "cloud-native-security-inspector:serve" + }, + "configurations": { + "production": { + "devServerTarget": "cloud-native-security-inspector:serve:production" + } + } + }, + "cypress-open": { + "builder": "@cypress/schematic:cypress", + "options": { + "watch": true, + "headless": false + } + }, + "ct": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "cloud-native-security-inspector:serve", + "watch": true, + "headless": false, + "testingType": "component" + }, + "configurations": { + "development": { + "devServerTarget": "cloud-native-security-inspector:serve:development" + } + } + }, + "e2e": { + "builder": "@cypress/schematic:cypress", + "options": { + "devServerTarget": "cloud-native-security-inspector:serve", + "watch": true, + "headless": false + }, + "configurations": { + "production": { + "devServerTarget": "cloud-native-security-inspector:serve:production" + } + } } } } @@ -128,4 +173,4 @@ "@angular-eslint/schematics" ] } -} +} \ No newline at end of file diff --git a/src/frontend/cypress.config.ts b/src/frontend/cypress.config.ts new file mode 100644 index 00000000..379e455a --- /dev/null +++ b/src/frontend/cypress.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + + e2e: { + baseUrl: null, + supportFile: false + }, + + + component: { + devServer: { + framework: 'angular', + bundler: 'webpack', + }, + specPattern: '**/*.cy.ts' + } + +}) \ No newline at end of file diff --git a/src/frontend/cypress/e2e/1-getting-started/todo.cy.js b/src/frontend/cypress/e2e/1-getting-started/todo.cy.js new file mode 100644 index 00000000..4768ff92 --- /dev/null +++ b/src/frontend/cypress/e2e/1-getting-started/todo.cy.js @@ -0,0 +1,143 @@ +/// + +// Welcome to Cypress! +// +// This spec file contains a variety of sample tests +// for a todo list app that are designed to demonstrate +// the power of writing tests in Cypress. +// +// To learn more about how Cypress works and +// what makes it such an awesome testing tool, +// please read our getting started guide: +// https://on.cypress.io/introduction-to-cypress + +describe('example to-do app', () => { + beforeEach(() => { + // Cypress starts out with a blank slate for each test + // so we must tell it to visit our website with the `cy.visit()` command. + // Since we want to visit the same URL at the start of all our tests, + // we include it in our beforeEach function so that it runs before each test + cy.visit('https://example.cypress.io/todo') + }) + + it('displays two todo items by default', () => { + // We use the `cy.get()` command to get all elements that match the selector. + // Then, we use `should` to assert that there are two matched items, + // which are the two default items. + cy.get('.todo-list li').should('have.length', 2) + + // We can go even further and check that the default todos each contain + // the correct text. We use the `first` and `last` functions + // to get just the first and last matched elements individually, + // and then perform an assertion with `should`. + cy.get('.todo-list li').first().should('have.text', 'Pay electric bill') + cy.get('.todo-list li').last().should('have.text', 'Walk the dog') + }) + + it('can add new todo items', () => { + // We'll store our item text in a variable so we can reuse it + const newItem = 'Feed the cat' + + // Let's get the input element and use the `type` command to + // input our new list item. After typing the content of our item, + // we need to type the enter key as well in order to submit the input. + // This input has a data-test attribute so we'll use that to select the + // element in accordance with best practices: + // https://on.cypress.io/selecting-elements + cy.get('[data-test=new-todo]').type(`${newItem}{enter}`) + + // Now that we've typed our new item, let's check that it actually was added to the list. + // Since it's the newest item, it should exist as the last element in the list. + // In addition, with the two default items, we should have a total of 3 elements in the list. + // Since assertions yield the element that was asserted on, + // we can chain both of these assertions together into a single statement. + cy.get('.todo-list li') + .should('have.length', 3) + .last() + .should('have.text', newItem) + }) + + it('can check off an item as completed', () => { + // In addition to using the `get` command to get an element by selector, + // we can also use the `contains` command to get an element by its contents. + // However, this will yield the