Introduce Cypress
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
cd72059536
commit
77d286ebb6
|
@ -4,6 +4,7 @@ stages:
|
||||||
- deps
|
- deps
|
||||||
- front
|
- front
|
||||||
- back
|
- back
|
||||||
|
- e2e
|
||||||
- deploy
|
- deploy
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
|
@ -19,6 +20,7 @@ variables:
|
||||||
MOBILIZON_DATABASE_DBNAME: $POSTGRES_DB
|
MOBILIZON_DATABASE_DBNAME: $POSTGRES_DB
|
||||||
MOBILIZON_DATABASE_HOST: $POSTGRES_HOST
|
MOBILIZON_DATABASE_HOST: $POSTGRES_HOST
|
||||||
GEOLITE_CITIES_PATH: "/usr/share/GeoIP/GeoLite2-City.mmdb"
|
GEOLITE_CITIES_PATH: "/usr/share/GeoIP/GeoLite2-City.mmdb"
|
||||||
|
MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true"
|
||||||
|
|
||||||
setup_elixir_deps:
|
setup_elixir_deps:
|
||||||
stage: deps
|
stage: deps
|
||||||
|
@ -144,3 +146,26 @@ mix:
|
||||||
paths:
|
paths:
|
||||||
- deps
|
- deps
|
||||||
- _build
|
- _build
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
stage: e2e
|
||||||
|
services:
|
||||||
|
- name: mdillon/postgis:10
|
||||||
|
alias: postgres
|
||||||
|
script:
|
||||||
|
- mix deps.get
|
||||||
|
- cd js
|
||||||
|
- yarn install
|
||||||
|
- yarn run build
|
||||||
|
- cd ../
|
||||||
|
- MIX_ENV=e2e mix ecto.create
|
||||||
|
- MIX_ENV=e2e mix ecto.migrate
|
||||||
|
- MIX_ENV=e2e mix phx.server &
|
||||||
|
- cd js
|
||||||
|
- npx wait-on http://localhost:4000
|
||||||
|
- npx cypress run --record --parallel --key $CYPRESS_KEY
|
||||||
|
artifacts:
|
||||||
|
expire_in: 2 day
|
||||||
|
paths:
|
||||||
|
- js/tests/e2e/screenshots/**/*.png
|
||||||
|
- js/tests/e2e/videos/**/*.mp4
|
|
@ -3,7 +3,7 @@
|
||||||
#
|
#
|
||||||
# This configuration file is loaded before any dependency and
|
# This configuration file is loaded before any dependency and
|
||||||
# is restricted to this project.
|
# is restricted to this project.
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# General application configuration
|
# General application configuration
|
||||||
config :mobilizon,
|
config :mobilizon,
|
||||||
|
@ -71,10 +71,6 @@ config :logger, :console,
|
||||||
format: "$time $metadata[$level] $message\n",
|
format: "$time $metadata[$level] $message\n",
|
||||||
metadata: [:request_id]
|
metadata: [:request_id]
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
|
||||||
# of this file so it overrides the configuration defined above.
|
|
||||||
import_config "#{Mix.env()}.exs"
|
|
||||||
|
|
||||||
config :mobilizon, MobilizonWeb.Guardian,
|
config :mobilizon, MobilizonWeb.Guardian,
|
||||||
issuer: "mobilizon",
|
issuer: "mobilizon",
|
||||||
secret_key: "ty0WM7YBE3ojvxoUQxo8AERrNpfbXnIJ82ovkPdqbUFw31T5LcK8wGjaOiReVQjo"
|
secret_key: "ty0WM7YBE3ojvxoUQxo8AERrNpfbXnIJ82ovkPdqbUFw31T5LcK8wGjaOiReVQjo"
|
||||||
|
@ -136,3 +132,7 @@ config :mobilizon, Mobilizon.Service.Geospatial.GoogleMaps,
|
||||||
|
|
||||||
config :mobilizon, Mobilizon.Service.Geospatial.MapQuest,
|
config :mobilizon, Mobilizon.Service.Geospatial.MapQuest,
|
||||||
api_key: System.get_env("GEOSPATIAL_MAP_QUEST_API_KEY") || nil
|
api_key: System.get_env("GEOSPATIAL_MAP_QUEST_API_KEY") || nil
|
||||||
|
|
||||||
|
# Import environment specific config. This must remain at the bottom
|
||||||
|
# of this file so it overrides the configuration defined above.
|
||||||
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
# For development, we disable any cache and enable
|
# For development, we disable any cache and enable
|
||||||
# debugging and code reloading.
|
# debugging and code reloading.
|
||||||
|
|
24
config/e2e.exs
Normal file
24
config/e2e.exs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import Config
|
||||||
|
|
||||||
|
import_config "dev.exs"
|
||||||
|
|
||||||
|
config :mobilizon, MobilizonWeb.Endpoint,
|
||||||
|
http: [
|
||||||
|
port: 4000
|
||||||
|
],
|
||||||
|
url: [
|
||||||
|
host: "localhost",
|
||||||
|
port: 4000,
|
||||||
|
scheme: "http"
|
||||||
|
],
|
||||||
|
debug_errors: true,
|
||||||
|
code_reloader: false,
|
||||||
|
check_origin: false,
|
||||||
|
# Somehow this can't be merged properly with the dev config some we got this…
|
||||||
|
watchers: [
|
||||||
|
yarn: [cd: Path.expand("../js", __DIR__)]
|
||||||
|
]
|
||||||
|
|
||||||
|
config :mobilizon, sql_sandbox: true
|
||||||
|
|
||||||
|
config :mobilizon, Mobilizon.Storage.Repo, pool: Ecto.Adapters.SQL.Sandbox
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
config :mobilizon, MobilizonWeb.Endpoint,
|
config :mobilizon, MobilizonWeb.Endpoint,
|
||||||
http: [:inet6, port: System.get_env("MOBILIZON_INSTANCE_PORT") || 4000],
|
http: [:inet6, port: System.get_env("MOBILIZON_INSTANCE_PORT") || 4000],
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use Mix.Config
|
import Config
|
||||||
|
|
||||||
config :mobilizon, :instance,
|
config :mobilizon, :instance,
|
||||||
name: "Test instance",
|
name: "Test instance",
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
FROM elixir:latest
|
FROM elixir:latest
|
||||||
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
|
LABEL maintainer="Thomas Citharel <tcit@tcit.fr>"
|
||||||
|
|
||||||
ENV REFRESHED_AT=2019-07-03
|
ENV REFRESHED_AT=2019-10-06
|
||||||
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg
|
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2
|
||||||
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash && apt-get install nodejs -yq
|
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash && apt-get install nodejs -yq
|
||||||
RUN npm install -g yarn
|
RUN npm install -g yarn wait-on
|
||||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
RUN mix local.hex --force && mix local.rebar --force
|
RUN mix local.hex --force && mix local.rebar --force
|
||||||
RUN curl http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz --output GeoLite2-City.tar.gz -s && tar zxf GeoLite2-City.tar.gz && mkdir -p /usr/share/GeoIP && mv GeoLite2-City_*/GeoLite2-City.mmdb /usr/share/GeoIP/GeoLite2-City.mmdb
|
RUN curl http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz --output GeoLite2-City.tar.gz -s && tar zxf GeoLite2-City.tar.gz && mkdir -p /usr/share/GeoIP && mv GeoLite2-City_*/GeoLite2-City.mmdb /usr/share/GeoIP/GeoLite2-City.mmdb
|
||||||
|
|
2
js/.gitignore
vendored
2
js/.gitignore
vendored
|
@ -3,6 +3,8 @@ node_modules
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
/tests/e2e/reports/
|
/tests/e2e/reports/
|
||||||
|
/tests/e2e/screenshots/
|
||||||
|
/tests/e2e/videos/
|
||||||
selenium-debug.log
|
selenium-debug.log
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
|
|
7
js/cypress.json
Normal file
7
js/cypress.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"pluginsFile": "tests/e2e/plugins/index.js",
|
||||||
|
"projectId": "86dpkx",
|
||||||
|
"baseUrl": "http://localhost:4000",
|
||||||
|
"viewportWidth": 1920,
|
||||||
|
"viewportHeight": 1080
|
||||||
|
}
|
|
@ -4,11 +4,11 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
|
"test:unit": "vue-cli-service test:unit",
|
||||||
|
"test:e2e": "vue-cli-service test:e2e",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"analyze-bundle": "yarn run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json",
|
"analyze-bundle": "yarn run build -- --report-json && webpack-bundle-analyzer ../priv/static/report.json",
|
||||||
"dev": "vue-cli-service build --watch",
|
"dev": "vue-cli-service build --watch",
|
||||||
"test:e2e": "vue-cli-service test:e2e",
|
|
||||||
"test:unit": "vue-cli-service test:unit",
|
|
||||||
"vue-i18n-extract": "vue-i18n-extract"
|
"vue-i18n-extract": "vue-i18n-extract"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
"@types/lodash": "^4.14.141",
|
"@types/lodash": "^4.14.141",
|
||||||
"@types/mocha": "^5.2.6",
|
"@types/mocha": "^5.2.6",
|
||||||
"@vue/cli-plugin-babel": "^3.6.0",
|
"@vue/cli-plugin-babel": "^3.6.0",
|
||||||
"@vue/cli-plugin-e2e-nightwatch": "^3.6.0",
|
"@vue/cli-plugin-e2e-cypress": "^4.0.0-rc.7",
|
||||||
"@vue/cli-plugin-pwa": "^3.6.0",
|
"@vue/cli-plugin-pwa": "^3.6.0",
|
||||||
"@vue/cli-plugin-typescript": "^3.6.0",
|
"@vue/cli-plugin-typescript": "^3.6.0",
|
||||||
"@vue/cli-plugin-unit-mocha": "^3.6.0",
|
"@vue/cli-plugin-unit-mocha": "^3.6.0",
|
||||||
|
|
|
@ -87,6 +87,7 @@ export default class App extends Vue {
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: #f6f7f8;
|
// background: #f7f8fa;
|
||||||
|
background: #ebebeb;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<b-navbar type="is-secondary" shadow wrapper-class="container">
|
<b-navbar type="is-secondary" wrapper-class="container">
|
||||||
<template slot="brand">
|
<template slot="brand">
|
||||||
<b-navbar-item tag="router-link" :to="{ name: RouteName.HOME }"><logo /></b-navbar-item>
|
<b-navbar-item tag="router-link" :to="{ name: RouteName.HOME }"><logo /></b-navbar-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="container" v-if="config">
|
|
||||||
<section class="hero is-info" v-if="!currentUser.id || !currentActor">
|
|
||||||
<div class="hero-body">
|
|
||||||
<div>
|
<div>
|
||||||
|
<section class="hero is-medium is-light is-bold" v-if="!currentUser.id || !currentActor.id">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
<h1 class="title">{{ config.name }}</h1>
|
<h1 class="title">{{ config.name }}</h1>
|
||||||
<h2 class="subtitle">{{ config.description }}</h2>
|
<h2 class="subtitle">{{ config.description }}</h2>
|
||||||
<router-link class="button" :to="{ name: RouteName.REGISTER }" v-if="config.registrationsOpen">
|
<router-link class="button" :to="{ name: RouteName.REGISTER }" v-if="config.registrationsOpen">
|
||||||
|
@ -12,9 +14,19 @@
|
||||||
{{ $t("This instance isn't opened to registrations, but you can register on other instances.") }}
|
{{ $t("This instance isn't opened to registrations, but you can register on other instances.") }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<div class="card-image">
|
||||||
|
<figure class="image is-square">
|
||||||
|
<img src="https://joinmobilizon.org/img/en/events-mobilizon.png" />
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section v-else-if="currentActor">
|
<div class="container" v-if="config">
|
||||||
|
<section v-if="currentActor.id">
|
||||||
<b-message type="is-info">
|
<b-message type="is-info">
|
||||||
{{ $t('Welcome back {username}', { username: currentActor.displayName() }) }}
|
{{ $t('Welcome back {username}', { username: currentActor.displayName() }) }}
|
||||||
</b-message>
|
</b-message>
|
||||||
|
@ -24,7 +36,7 @@
|
||||||
{{ $t("Upcoming") }}
|
{{ $t("Upcoming") }}
|
||||||
</h3>
|
</h3>
|
||||||
<b-loading :active.sync="$apollo.loading"></b-loading>
|
<b-loading :active.sync="$apollo.loading"></b-loading>
|
||||||
<div v-for="row in goingToEvents" class="upcoming-events">
|
<div v-for="row in goingToEvents" class="upcoming-events" :key="row[0]">
|
||||||
<span class="date-component-container" v-if="isInLessThanSevenDays(row[0])">
|
<span class="date-component-container" v-if="isInLessThanSevenDays(row[0])">
|
||||||
<date-component :date="row[0]"></date-component>
|
<date-component :date="row[0]"></date-component>
|
||||||
<h3 class="subtitle"
|
<h3 class="subtitle"
|
||||||
|
@ -82,6 +94,7 @@
|
||||||
</b-message>
|
</b-message>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -260,6 +273,8 @@ export default class Home extends Vue {
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import "@/variables.scss";
|
||||||
|
|
||||||
.search-autocomplete {
|
.search-autocomplete {
|
||||||
border: 1px solid #dbdbdb;
|
border: 1px solid #dbdbdb;
|
||||||
color: rgba(0, 0, 0, 0.87);
|
color: rgba(0, 0, 0, 0.87);
|
||||||
|
@ -292,4 +307,14 @@ export default class Home extends Vue {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section.hero {
|
||||||
|
margin-top: -3px;
|
||||||
|
background: lighten($secondary, 20%);
|
||||||
|
|
||||||
|
.column figure.image img {
|
||||||
|
width: 480px;
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,8 +8,7 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<div class="container">
|
<div class="columns">
|
||||||
<div class="columns is-mobile">
|
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<h3 class="title">{{ $t('Features') }}</h3>
|
<h3 class="title">{{ $t('Features') }}</h3>
|
||||||
|
@ -99,7 +98,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
24
js/tests/e2e/plugins/index.js
Normal file
24
js/tests/e2e/plugins/index.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// https://docs.cypress.io/guides/guides/plugins-guide.html
|
||||||
|
|
||||||
|
// if you need a custom webpack configuration you can uncomment the following import
|
||||||
|
// and then use the `file:preprocessor` event
|
||||||
|
// as explained in the cypress docs
|
||||||
|
// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
|
||||||
|
|
||||||
|
/* eslint-disable import/no-extraneous-dependencies, global-require, arrow-body-style */
|
||||||
|
// const webpack = require('@cypress/webpack-preprocessor')
|
||||||
|
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
// on('file:preprocessor', webpack({
|
||||||
|
// webpackOptions: require('@vue/cli-service/webpack.config'),
|
||||||
|
// watchOptions: {}
|
||||||
|
// }))
|
||||||
|
|
||||||
|
return Object.assign({}, config, {
|
||||||
|
fixturesFolder: 'tests/e2e/fixtures',
|
||||||
|
integrationFolder: 'tests/e2e/specs',
|
||||||
|
screenshotsFolder: 'tests/e2e/screenshots',
|
||||||
|
videosFolder: 'tests/e2e/videos',
|
||||||
|
supportFile: 'tests/e2e/support/index.js'
|
||||||
|
})
|
||||||
|
}
|
2
js/tests/e2e/specs/browser-language.js
Normal file
2
js/tests/e2e/specs/browser-language.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
// Set the en-US language just in case
|
||||||
|
export const onBeforeLoad = (window) => Object.defineProperty(window.navigator, 'language', { value: 'en-US' });
|
42
js/tests/e2e/specs/dashboard.js
Normal file
42
js/tests/e2e/specs/dashboard.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// https://docs.cypress.io/api/introduction/api.html
|
||||||
|
import { onBeforeLoad } from './browser-language';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.restoreLocalStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.saveLocalStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Homepage', () => {
|
||||||
|
it('Checks the footer', () => {
|
||||||
|
cy.visit('/', { onBeforeLoad });
|
||||||
|
cy.get('#mobilizon').find('footer').contains('The Mobilizon Contributors');
|
||||||
|
|
||||||
|
cy.contains('About').should('have.attr', 'href').and('eq', 'https://joinmobilizon.org');
|
||||||
|
|
||||||
|
cy.contains('License').should('have.attr', 'href').and('eq', 'https://framagit.org/framasoft/mobilizon/blob/master/LICENSE');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to register from the hero section', () => {
|
||||||
|
cy.visit('/', { onBeforeLoad });
|
||||||
|
|
||||||
|
cy.get('.hero-body').contains('Sign up').click();
|
||||||
|
cy.url().should('include', '/register/user');
|
||||||
|
|
||||||
|
});
|
||||||
|
it('Tries to register from the navbar', () => {
|
||||||
|
cy.visit('/', { onBeforeLoad });
|
||||||
|
|
||||||
|
cy.get('nav.navbar').contains('Sign up').click();
|
||||||
|
cy.url().should('include', '/register/user');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to connect from the navbar', () => {
|
||||||
|
cy.visit('/', { onBeforeLoad });
|
||||||
|
|
||||||
|
cy.get('nav.navbar').contains('Log in').click();
|
||||||
|
cy.url().should('include', '/login');
|
||||||
|
});
|
||||||
|
});
|
45
js/tests/e2e/specs/login.js
Normal file
45
js/tests/e2e/specs/login.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { onBeforeLoad } from './browser-language';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.restoreLocalStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.saveLocalStorage();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Login', () => {
|
||||||
|
it('Tests that everything is present', () => {
|
||||||
|
cy.visit('/login', { onBeforeLoad });
|
||||||
|
|
||||||
|
cy.get('form .field').first().contains('label', 'Email');
|
||||||
|
cy.get('form .field').last().contains('label', 'Password');
|
||||||
|
cy.get('form').contains('button.button', 'Login');
|
||||||
|
cy.get('form').contains('.control a.button', 'Forgot your password ?').click();
|
||||||
|
cy.url().should('include', '/password-reset/send');
|
||||||
|
cy.go('back');
|
||||||
|
|
||||||
|
cy.get('form').contains('.control a.button', 'Register').click();
|
||||||
|
cy.url().should('include', '/register/user');
|
||||||
|
|
||||||
|
cy.go('back');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to login with incorrect credentials', () => {
|
||||||
|
cy.visit('/login', { onBeforeLoad });
|
||||||
|
cy.get('input[type=email]').type('notanemail').should('have.value', 'notanemail');
|
||||||
|
cy.get('input[type=password]').click();
|
||||||
|
cy.contains('button.button.is-primary.is-large', 'Login').click();
|
||||||
|
cy.get('form .field').first().contains('p.help.is-danger', 'Please include an \'@\' in the email address.');
|
||||||
|
cy.get('form .field').last().contains('p.help.is-danger', 'Please fill out this field.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tries to login with invalid credentials', () => {
|
||||||
|
cy.visit('/login', { onBeforeLoad });
|
||||||
|
cy.get('input[type=email]').type('test@email.com').should('have.value', 'test@email.com');
|
||||||
|
cy.get('input[type=password]').type('badPassword').should('have.value', 'badPassword');
|
||||||
|
cy.contains('button.button.is-primary.is-large', 'Login').click();
|
||||||
|
|
||||||
|
cy.contains('.message.is-danger', 'User with email not found');
|
||||||
|
});
|
||||||
|
});
|
69
js/tests/e2e/specs/register.js
Normal file
69
js/tests/e2e/specs/register.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { onBeforeLoad } from './browser-language';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.restoreLocalStorage();
|
||||||
|
cy.checkoutSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.saveLocalStorage();
|
||||||
|
cy.dropSession();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Registration', () => {
|
||||||
|
it('Tests that everything is present', () => {
|
||||||
|
cy.visit('/register/user', { onBeforeLoad });
|
||||||
|
|
||||||
|
cy.get('form .field').first().contains('label', 'Email');
|
||||||
|
cy.get('form .field').eq(1).contains('label', 'Password');
|
||||||
|
|
||||||
|
cy.get('input[type=email]').click();
|
||||||
|
cy.get('input[type=password]').type('short').should('have.value', 'short');
|
||||||
|
cy.get('form').contains('button.button.is-primary', 'Register');
|
||||||
|
cy.get('form .field').first().contains('p.help.is-danger', 'Please fill out this field.');
|
||||||
|
|
||||||
|
cy.get('form').contains('.control a.button', 'Didn\'t receive the instructions ?').click();
|
||||||
|
cy.url().should('include', '/resend-instructions');
|
||||||
|
cy.go('back');
|
||||||
|
|
||||||
|
cy.get('form').contains('.control a.button', 'Login').click();
|
||||||
|
cy.url().should('include', '/login');
|
||||||
|
|
||||||
|
cy.go('back');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Tests that registration works', () => {
|
||||||
|
cy.visit('/register/user', { onBeforeLoad });
|
||||||
|
cy.get('input[type=email]').type('user@email.com');
|
||||||
|
cy.get('input[type=password]').type('userPassword');
|
||||||
|
cy.get('form').contains('button.button.is-primary', 'Register').click();
|
||||||
|
|
||||||
|
cy.url().should('include', '/register/profile');
|
||||||
|
cy.get('form .field').first().contains('label', 'Username').parent().find('input').type('tester');
|
||||||
|
cy.get('form .field').eq(2).contains('label', 'Displayed name').parent().find('input').type('tester account');
|
||||||
|
cy.get('form .field').eq(3).contains('label', 'Description').parent().find('textarea').type('This is a test account');
|
||||||
|
cy.get('form .field').last().contains('button', 'Create my profile').click();
|
||||||
|
|
||||||
|
cy.contains('article.message.is-success', 'Your account is nearly ready, tester').contains('A validation email was sent to user@email.com');
|
||||||
|
|
||||||
|
cy.visit('/sent_emails');
|
||||||
|
|
||||||
|
cy.get('iframe')
|
||||||
|
.first()
|
||||||
|
.iframeLoaded()
|
||||||
|
.its('document')
|
||||||
|
.getInDocument('a')
|
||||||
|
.eq(1)
|
||||||
|
.contains('Activate my account')
|
||||||
|
.invoke('attr', 'href')
|
||||||
|
.then(href => {
|
||||||
|
cy.visit(href);
|
||||||
|
});
|
||||||
|
|
||||||
|
// cy.url().should('include', '/validate/');
|
||||||
|
// cy.contains('Your account is being validated');
|
||||||
|
cy.location().should((loc) => {
|
||||||
|
expect(loc.pathname).to.eq('/');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,14 +0,0 @@
|
||||||
// For authoring Nightwatch tests, see
|
|
||||||
// http://nightwatchjs.org/guide#usage
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
'default e2e tests': (browser) => {
|
|
||||||
browser
|
|
||||||
.url(process.env.VUE_DEV_SERVER_URL)
|
|
||||||
.waitForElementVisible('#app', 5000)
|
|
||||||
.assert.elementPresent('.hello')
|
|
||||||
.assert.containsText('h1', 'Welcome to Your Vue.js App')
|
|
||||||
.assert.elementCount('img', 1)
|
|
||||||
.end();
|
|
||||||
},
|
|
||||||
};
|
|
129
js/tests/e2e/support/commands.js
Normal file
129
js/tests/e2e/support/commands.js
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
|
let LOCAL_STORAGE_MEMORY = {};
|
||||||
|
|
||||||
|
Cypress.Commands.add("saveLocalStorage", () => {
|
||||||
|
Object.keys(localStorage).forEach(key => {
|
||||||
|
LOCAL_STORAGE_MEMORY[key] = localStorage[key];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add("restoreLocalStorage", () => {
|
||||||
|
Object.keys(LOCAL_STORAGE_MEMORY).forEach(key => {
|
||||||
|
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('checkoutSession', async () => {
|
||||||
|
const response = await fetch('/sandbox', {
|
||||||
|
cache: 'no-store',
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionId = await response.text();
|
||||||
|
return Cypress.env('sessionId', sessionId);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('dropSession', () =>
|
||||||
|
cy.waitForFetches().then(() =>
|
||||||
|
fetch('/sandbox', {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'x-session-id': Cypress.env('sessionId') },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const increaseFetches = () => {
|
||||||
|
const count = Cypress.env('fetchCount') || 0;
|
||||||
|
Cypress.env('fetchCount', count + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const decreaseFetches = () => {
|
||||||
|
const count = Cypress.env('fetchCount') || 0;
|
||||||
|
Cypress.env('fetchCount', count - 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTrackableFetchWithSessionId = fetch => (fetchUrl, fetchOptions) => {
|
||||||
|
const { headers } = fetchOptions;
|
||||||
|
const modifiedHeaders = Object.assign(
|
||||||
|
{ 'x-session-id': Cypress.env('sessionId') },
|
||||||
|
headers,
|
||||||
|
);
|
||||||
|
|
||||||
|
const modifiedOptions = Object.assign({}, fetchOptions, {
|
||||||
|
headers: modifiedHeaders,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fetch(fetchUrl, modifiedOptions)
|
||||||
|
.then(result => {
|
||||||
|
decreaseFetches();
|
||||||
|
return Promise.resolve(result);
|
||||||
|
})
|
||||||
|
.catch(result => {
|
||||||
|
decreaseFetches();
|
||||||
|
return Promise.reject(result);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
Cypress.on('window:before:load', win => {
|
||||||
|
cy.stub(win, 'fetch', buildTrackableFetchWithSessionId(fetch));
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('waitForFetches', () => {
|
||||||
|
if (Cypress.env('fetchCount') <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.wait(100).then(() => cy.waitForFetches());
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add(
|
||||||
|
'iframeLoaded',
|
||||||
|
{ prevSubject: 'element' },
|
||||||
|
($iframe) => {
|
||||||
|
const contentWindow = $iframe.prop('contentWindow')
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (
|
||||||
|
contentWindow &&
|
||||||
|
contentWindow.document.readyState === 'complete'
|
||||||
|
) {
|
||||||
|
resolve(contentWindow)
|
||||||
|
} else {
|
||||||
|
$iframe.on('load', () => {
|
||||||
|
resolve(contentWindow)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Cypress.Commands.add(
|
||||||
|
'getInDocument',
|
||||||
|
{ prevSubject: 'document' },
|
||||||
|
(document, selector) => Cypress.$(selector, document)
|
||||||
|
)
|
20
js/tests/e2e/support/index.js
Normal file
20
js/tests/e2e/support/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
12
js/tests/e2e/tsconfig.json
Normal file
12
js/tests/e2e/tsconfig.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"baseUrl": "../node_modules",
|
||||||
|
"types": [
|
||||||
|
"cypress"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"**/*.*"
|
||||||
|
]
|
||||||
|
}
|
1073
js/yarn.lock
1073
js/yarn.lock
File diff suppressed because it is too large
Load diff
|
@ -4,13 +4,21 @@ defmodule MobilizonWeb.Endpoint do
|
||||||
"""
|
"""
|
||||||
use Phoenix.Endpoint, otp_app: :mobilizon
|
use Phoenix.Endpoint, otp_app: :mobilizon
|
||||||
|
|
||||||
|
# For e2e tests
|
||||||
|
if Application.get_env(:mobilizon, :sql_sandbox) do
|
||||||
|
plug(Phoenix.Ecto.SQL.Sandbox,
|
||||||
|
at: "/sandbox",
|
||||||
|
header: "x-session-id",
|
||||||
|
repo: Mobilizon.Storage.Repo
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
plug(MobilizonWeb.Plugs.UploadedMedia)
|
||||||
|
|
||||||
# Serve at "/" the static files from "priv/static" directory.
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
#
|
#
|
||||||
# You should set gzip to true if you are running phoenix.digest
|
# You should set gzip to true if you are running phoenix.digest
|
||||||
# when deploying your static files in production.
|
# when deploying your static files in production.
|
||||||
|
|
||||||
plug(MobilizonWeb.Plugs.UploadedMedia)
|
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
Plug.Static,
|
Plug.Static,
|
||||||
at: "/",
|
at: "/",
|
||||||
|
|
|
@ -118,7 +118,7 @@ defmodule MobilizonWeb.Router do
|
||||||
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
||||||
end
|
end
|
||||||
|
|
||||||
if Mix.env() == :dev do
|
if Mix.env() in [:dev, :e2e] do
|
||||||
# If using Phoenix
|
# If using Phoenix
|
||||||
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
|
forward("/sent_emails", Bamboo.SentEmailViewerPlug)
|
||||||
end
|
end
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -99,7 +99,7 @@ defmodule Mobilizon.Mixfile do
|
||||||
{:html_sanitize_ex, "~> 1.3.0"},
|
{:html_sanitize_ex, "~> 1.3.0"},
|
||||||
{:ex_cldr_dates_times, "~> 2.0"},
|
{:ex_cldr_dates_times, "~> 2.0"},
|
||||||
# Dev and test dependencies
|
# Dev and test dependencies
|
||||||
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
{:phoenix_live_reload, "~> 1.2", only: [:dev, :e2e]},
|
||||||
{:ex_machina, "~> 2.3", only: [:dev, :test]},
|
{:ex_machina, "~> 2.3", only: [:dev, :test]},
|
||||||
{:excoveralls, "~> 0.10", only: :test},
|
{:excoveralls, "~> 0.10", only: :test},
|
||||||
{:ex_doc, "~> 0.21.1", only: [:dev, :test], runtime: false},
|
{:ex_doc, "~> 0.21.1", only: [:dev, :test], runtime: false},
|
||||||
|
|
Loading…
Reference in a new issue