Skip to content

Commit

Permalink
test: setup E2E tests with Cypress (#572)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphodn authored May 7, 2024
1 parent a73297f commit c4f6a47
Show file tree
Hide file tree
Showing 19 changed files with 2,487 additions and 12 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: End-to-end tests

on: push

jobs:
cypress-run:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4
# Install npm dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v6
with:
start: yarn dev
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,9 @@ We use the [yarn](https://yarnpkg.com/getting-started/install) for package manag
```sh
yarn build
```

## Tests

```sh
yarn test
```
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Works with an Open Prices server, see https://github.com/openfoodfacts/open-pric

## Contribute

See [CONTRIBUTING.md](https://github.com/openfoodfacts/open-prices-frontend/blob/main/CONTRIBUTING.md)
See [CONTRIBUTING.md](https://github.com/openfoodfacts/open-prices-frontend/blob/master/CONTRIBUTING.md)

<details><summary><h2>Weekly meetings</h2></summary>
* see https://github.com/openfoodfacts/open-prices#weekly-meetings
Expand Down
16 changes: 16 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { defineConfig } = require("cypress");

module.exports = defineConfig({
e2e: {
specPattern: '**/*.cy.js',
fixturesFolder: 'tests/fixtures',
screenshotsFolder: 'tests/screenshots',
videosFolder: 'tests/videos',
downloadsFolder: 'tests/downloads',
supportFile: 'tests/support/e2e.js',
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: 'http://localhost:5173',
},
});
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
"build": "vite build",
"build-staging": "vite build --base=/app/ --mode preprod",
"build-prod": "vite build --base=/app/ --mode prod",
"preview": "vite preview"
"preview": "vite preview",
"cy:open": "cypress open",
"cy:run": "cypress run",
"test": "start-server-and-test dev http://localhost:5173 cy:run"
},
"dependencies": {
"@intlify/unplugin-vue-i18n": "^2.0.0",
Expand All @@ -33,7 +36,9 @@
"@vue/compiler-sfc": "^3.3.9",
"autoprefixer": "^10.4.16",
"cross-env": "^7.0.3",
"cypress": "^13.8.1",
"postcss": "^8.4.31",
"start-server-and-test": "^2.0.3",
"vite": "^4.5.0"
},
"resolutions": {
Expand Down
2 changes: 1 addition & 1 deletion src/components/PriceCard.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<v-card :id="'price_' + price.id">
<v-card :id="'price_' + price.id" data-name="price-card">
<v-container class="pa-2">
<v-row>
<v-col v-if="!hideProductImage" style="max-width:15%">
Expand Down
2 changes: 1 addition & 1 deletion src/components/PriceCountChip.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<v-chip label size="small" density="comfortable" :color="getColor()" class="mr-1">
<v-icon start icon="mdi-tag-outline"></v-icon>
<span v-if="withLabel">{{ $t('PriceCountChip.PriceCount', { count: count }) }}</span>
<span v-if="!withLabel">{{ count }}</span>
<span v-else id="price-count">{{ count }}</span>
</v-chip>
</template>

Expand Down
4 changes: 2 additions & 2 deletions src/components/ProductCard.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<template>
<v-card>
<v-card data-name="product-card">
<v-container class="pa-2">
<v-row>
<v-col style="max-width:25%">
<v-img v-if="product.image_url" :src="product.image_url" style="max-height:100px;width:100px" @click="goToProduct()"></v-img>
<v-img v-if="!product.image_url" :src="productImageDefault" style="height:100px;width:100px;filter:invert(.9);"></v-img>
</v-col>
<v-col style="max-width:75%">
<h3 @click="goToProduct()">{{ getProductTitle() }}</h3>
<h3 id="product-title" @click="goToProduct()">{{ getProductTitle() }}</h3>

<p>
<span>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProductMissingChip.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<v-chip label size="small" density="comfortable" prepend-icon="mdi-alert" color="error">
<v-chip label size="small" density="comfortable" prepend-icon="mdi-alert" color="error" data-name="product-missing-chip">
{{ $t('PriceCard.UnknownProduct') }}
</v-chip>
</template>
4 changes: 2 additions & 2 deletions src/views/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
<template v-slot:subtitle v-if="!loading">
<i18n-t keypath="Home.TodayPriceStat" :plural="todayPriceCount" tag="span">
<template v-slot:todayPriceNumber>
<span>{{ todayPriceCount }}</span>
<span id="price-count">{{ todayPriceCount }}</span>
</template>
</i18n-t>
</template>
Expand Down Expand Up @@ -95,7 +95,7 @@ export default {
methods: {
getTodayPriceCount() {
this.loading = true
return api.getPrices({ created__gte: utils.currentStartOfDay(), size: 2 })
return api.getPrices({ created__gte: utils.currentStartOfDay(), size: 1 })
.then((data) => {
this.todayPriceCount = data.total
this.loading = false
Expand Down
51 changes: 51 additions & 0 deletions tests/e2e/spec.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
describe('Basic tests', () => {
beforeEach(() => {
cy.intercept('GET', 'http://127.0.0.1:8000/api/v1/products?page=1&size=10&order_by=-price_count', { fixture: 'products.json' })
cy.intercept('GET', 'http://127.0.0.1:8000/api/v1/products/code/3011360030498', { fixture: 'product_3011360030498.json' })
cy.intercept('GET', 'http://127.0.0.1:8000/api/v1/prices?page=1&size=1&order_by=-created*', { fixture: 'prices.json' })
cy.intercept('GET', 'http://127.0.0.1:8000/api/v1/prices?page=1&size=10&order_by=-created', { fixture: 'prices.json' })
cy.intercept('GET', 'http://127.0.0.1:8000/api/v1/prices?page=1&size=10&order_by=-date&product_code=3011360030498', { fixture: 'product_3011360030498_prices.json' })
cy.intercept('GET', 'http://127.0.0.1:8000/api/v1/prices?page=1&size=10&order_by=-date&category_tag=en%3Apitted-apricot', { fixture: 'pitted_apricot_prices.json' })
})

it('loads the home page', () => {
cy.visit('/')
cy.contains('Welcome to Open Prices!') // en by default
cy.get('#price-count').contains('42')
})

it('displays the latest prices', () => {
cy.visit('/prices')
cy.contains('Welcome to Open Prices!').should('not.exist')
cy.get('[data-name="price-card"]').should('have.length', 10)
cy.contains('3564700428023') // unknown product
cy.get('[data-name="product-missing-chip"]').should('have.length', 1)
})

it('displays the top products', () => {
cy.visit('/products')
cy.contains('Welcome to Open Prices!').should('not.exist')
cy.get('[data-name="product-card"]').should('have.length', 10)
cy.contains('3973467869701') // unknown product
cy.contains('3250391696949') // unknown product
cy.get('[data-name="product-missing-chip"]').should('have.length', 2)
})

it('displays a product page', () => {
cy.visit('/products/3011360030498')
cy.contains('Welcome to Open Prices!').should('not.exist')
cy.get('#product-title').contains('Mayonnaise Classique')
cy.get('#price-count').contains('1')
cy.get('[data-name="product-missing-chip"]').should('have.length', 0)
cy.get('[data-name="price-card"]').should('have.length', 1)
})

it('displays a raw product page', () => {
cy.visit('/products/en:pitted-apricot')
cy.contains('Welcome to Open Prices!').should('not.exist')
cy.get('.v-card-title').contains('Pitted apricot')
// cy.get('#price-count').contains('1')
cy.get('[data-name="product-missing-chip"]').should('have.length', 2) // TODO: fix
cy.get('[data-name="price-card"]').should('have.length', 2)
})
})
104 changes: 104 additions & 0 deletions tests/fixtures/pitted_apricot_prices.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"items": [
{
"product_code": null,
"product_name": null,
"category_tag": "en:pitted-apricot",
"labels_tags": null,
"origins_tags": [
"en:unknown"
],
"price": 4.0,
"price_is_discounted": false,
"price_without_discount": null,
"price_per": "UNIT",
"currency": "AED",
"location_osm_id": 32520537,
"location_osm_type": "WAY",
"date": "2024-03-31",
"proof_id": 224,
"id": 760,
"product_id": null,
"location_id": 32,
"owner": "user1",
"created": "2024-03-31T22:10:53.037731+02:00",
"product": null,
"proof": {
"id": 224,
"file_path": "0001/TWZOnskQhz.webp",
"mimetype": "image/webp",
"type": "RECEIPT",
"owner": "user1",
"price_count": 9,
"created": "2024-03-20T01:40:46.098715+01:00"
},
"location": {
"osm_id": 32520537,
"osm_type": "WAY",
"price_count": 16,
"id": 32,
"osm_name": "Monoprix",
"osm_display_name": "Monoprix, Rue Lafayette, Hyper Centre, Secteur 2, Grenoble, Isère, Auvergne-Rhône-Alpes, France métropolitaine, 38000, France",
"osm_address_postcode": "38000",
"osm_address_city": "Grenoble",
"osm_address_country": "France",
"osm_lat": 45.1904063,
"osm_lon": 5.7286464,
"created": "2024-02-09T17:43:00.113125+01:00",
"updated": "2024-03-31T22:13:49.569736+02:00"
}
},
{
"product_code": null,
"product_name": null,
"category_tag": "en:pitted-apricot",
"labels_tags": null,
"origins_tags": [
"en:germany"
],
"price": 3.0,
"price_is_discounted": false,
"price_without_discount": null,
"price_per": "KILOGRAM",
"currency": "AED",
"location_osm_id": 32520537,
"location_osm_type": "WAY",
"date": "2024-03-20",
"proof_id": 224,
"id": 759,
"product_id": null,
"location_id": 32,
"owner": "user1",
"created": "2024-03-20T14:27:21.575599+01:00",
"product": null,
"proof": {
"id": 224,
"file_path": "0001/TWZOnskQhz.webp",
"mimetype": "image/webp",
"type": "RECEIPT",
"owner": "user1",
"price_count": 9,
"created": "2024-03-20T01:40:46.098715+01:00"
},
"location": {
"osm_id": 32520537,
"osm_type": "WAY",
"price_count": 16,
"id": 32,
"osm_name": "Monoprix",
"osm_display_name": "Monoprix, Rue Lafayette, Hyper Centre, Secteur 2, Grenoble, Isère, Auvergne-Rhône-Alpes, France métropolitaine, 38000, France",
"osm_address_postcode": "38000",
"osm_address_city": "Grenoble",
"osm_address_country": "France",
"osm_lat": 45.1904063,
"osm_lon": 5.7286464,
"created": "2024-02-09T17:43:00.113125+01:00",
"updated": "2024-03-31T22:13:49.569736+02:00"
}
}
],
"total": 2,
"page": 1,
"size": 10,
"pages": 1
}
Loading

0 comments on commit c4f6a47

Please sign in to comment.