Moving e2e testing to Cypress
This commit is contained in:
parent
1ed42892e3
commit
76c7aae5ff
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"baseUrl": "http://localhost:8080"
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
[{
|
||||
"email": "user1@mail.local",
|
||||
"name": "Kathy Jordan",
|
||||
"username": "kathy.jordan",
|
||||
"password": "4jpbly8g6a",
|
||||
"birthday": "08/08/1995",
|
||||
"foreman": false,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user2@mail.local",
|
||||
"name": "Natalie Jordan",
|
||||
"username": "natalie.jordan",
|
||||
"password": "kf343di74s",
|
||||
"birthday": "04/05/2011",
|
||||
"foreman": true,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user3@mail.local",
|
||||
"name": "Keith Li",
|
||||
"username": "keith.li",
|
||||
"password": "3ojvgk4fpv",
|
||||
"birthday": "11/04/2019",
|
||||
"foreman": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user4@mail.local",
|
||||
"name": "Pellegrino Scotto",
|
||||
"username": "pellegrino.scotto",
|
||||
"password": "e0ou92taw3",
|
||||
"birthday": "20/10/1983",
|
||||
"foreman": true,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user5@mail.local",
|
||||
"name": "William Torres",
|
||||
"username": "william.torres",
|
||||
"password": "0fso8sxxe0",
|
||||
"birthday": "16/07/2000",
|
||||
"foreman": false,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user6@mail.local",
|
||||
"name": "Napoleone Tomasetti",
|
||||
"username": "napoleone.tomasetti",
|
||||
"password": "so7ykv8a7g",
|
||||
"birthday": "27/12/1978",
|
||||
"foreman": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user7@mail.local",
|
||||
"name": "Gelsomina Murray",
|
||||
"username": "gelsomina.murray",
|
||||
"password": "x1js0s6zao",
|
||||
"birthday": "22/10/1994",
|
||||
"foreman": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user8@mail.local",
|
||||
"name": "Letizia Petrucelli",
|
||||
"username": "letizia.petrucelli",
|
||||
"password": "k1hsbdt3cv",
|
||||
"birthday": "24/04/1981",
|
||||
"foreman": true,
|
||||
"driver": false
|
||||
}, {
|
||||
"email": "user9@mail.local",
|
||||
"name": "Giampaolo Surian",
|
||||
"username": "giampaolo.surian",
|
||||
"password": "et52m65s4g",
|
||||
"birthday": "10/06/1972",
|
||||
"foreman": true,
|
||||
"driver": true
|
||||
}, {
|
||||
"email": "user10@mail.local",
|
||||
"name": "Cassandra Jensen",
|
||||
"username": "cassandra.jensen",
|
||||
"password": "9h3fb37ccw",
|
||||
"birthday": "28/10/1985",
|
||||
"foreman": true,
|
||||
"driver": false
|
||||
}]
|
|
@ -0,0 +1,111 @@
|
|||
describe("Installation", () => {
|
||||
before(() => {
|
||||
cy.exec("rm config.old.php", {failOnNonZeroExit: false});
|
||||
cy.exec("mv config.php config.old.php", {failOnNonZeroExit: false});
|
||||
cy.exec("touch install/runInstall.php", {failOnNonZeroExit: false});
|
||||
cy.visit("/");
|
||||
cy.get(".button").click();
|
||||
})
|
||||
it('Write wrong DB pwd and user', function () {
|
||||
cy.get("input[name='dbname']")
|
||||
.clear()
|
||||
.type("allerta_db_"+Date.now())
|
||||
|
||||
cy.get("input[name='uname']")
|
||||
.clear()
|
||||
.type("root_wrongpwd_"+Date.now())
|
||||
|
||||
cy.get("input[name='pwd']")
|
||||
.clear()
|
||||
.should('have.value', '')
|
||||
|
||||
cy.get(".button").click();
|
||||
cy.contains("Error establishing a database connection");
|
||||
cy.visit("/");
|
||||
cy.get(".button").click();
|
||||
})
|
||||
|
||||
it('Write correct DB pwd and user', function () {
|
||||
cy.get("input[name='dbname']")
|
||||
.clear()
|
||||
.type("allerta_db_"+Date.now())
|
||||
|
||||
cy.get("input[name='uname']")
|
||||
.clear()
|
||||
.type("root")
|
||||
.should('have.value', 'root')
|
||||
|
||||
cy.get("input[name='pwd']")
|
||||
.clear()
|
||||
.should('have.value', '')
|
||||
|
||||
cy.get(".button").click();
|
||||
cy.contains("Great job, man!");
|
||||
cy.get(".button").click();
|
||||
})
|
||||
|
||||
it('Finish installation', function () {
|
||||
cy.get("input[name='user_name']")
|
||||
.clear()
|
||||
.type("admin")
|
||||
.should('have.value', 'admin')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("password")
|
||||
.should('have.value', 'password')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Very weak')
|
||||
.should('have.class', 'short')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("passsword")
|
||||
.should('have.value', 'passsword')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Weak')
|
||||
.should('have.class', 'bad')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("Tr0ub4dour&3")
|
||||
.should('have.value', 'Tr0ub4dour&3')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Good')
|
||||
.should('have.class', 'good')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("#Tr0ub4dour&3#")
|
||||
.should('have.value', '#Tr0ub4dour&3#')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Strong')
|
||||
.should('have.class', 'strong')
|
||||
|
||||
cy.get("input[name='admin_password']")
|
||||
.clear()
|
||||
.type("correcthorsebatterystaple")
|
||||
.should('have.value', 'correcthorsebatterystaple')
|
||||
cy.get("#pass-strength-result")
|
||||
.should('have.text', 'Very strong')
|
||||
.should('have.class', 'strong')
|
||||
|
||||
cy.get("input[name='admin_visible']").check()
|
||||
|
||||
cy.get("input[name='admin_email']")
|
||||
.clear()
|
||||
.type("admin_email@mail.local")
|
||||
.should('have.value', 'admin_email@mail.local')
|
||||
|
||||
cy.get("input[name='owner']")
|
||||
.clear()
|
||||
.type("owner")
|
||||
.should('have.value', 'owner')
|
||||
|
||||
cy.get(".button").click();
|
||||
cy.contains("Great job, man!");
|
||||
cy.get(".login").click();
|
||||
cy.contains("Login");
|
||||
cy.get(".acceptcookies").should('be.visible');
|
||||
})
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
describe("Login and logout", () => {
|
||||
it('Login', function () {
|
||||
cy.login()
|
||||
cy.contains("Logs").click()
|
||||
cy.get("#list").contains("Login")
|
||||
cy.visit("/logout.php")
|
||||
cy.contains("Login")
|
||||
})
|
||||
|
||||
it('Logout', function () {
|
||||
cy.login()
|
||||
cy.contains("Logs").click()
|
||||
cy.get("#list").contains("Logout")
|
||||
})
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
describe("Availability", () => {
|
||||
beforeEach(() => {
|
||||
cy.login()
|
||||
})
|
||||
it('Change availability to available', function () {
|
||||
cy.contains('Active').click()
|
||||
cy.on('window:alert',(txt)=>{
|
||||
expect(txt).to.contains('Thanks, admin, you have given your availability in case of alert.');
|
||||
})
|
||||
cy.get(".fa-check").should('be.visible')
|
||||
cy.contains("Logs").click()
|
||||
cy.contains("Attivazione disponibilita'")
|
||||
cy.visit("/")
|
||||
})
|
||||
|
||||
it('Change availability to not available', function () {
|
||||
cy.contains('Not Active').click()
|
||||
cy.on('window:alert',(txt)=>{
|
||||
expect(txt).to.contains('Thanks, admin, you have removed your availability in case of alert.');
|
||||
})
|
||||
cy.get(".fa-times").should('be.visible')
|
||||
cy.contains("Logs").click()
|
||||
cy.contains("Rimozione disponibilita'")
|
||||
cy.visit("/")
|
||||
})
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
describe("User management", () => {
|
||||
before(() => {
|
||||
cy.login()
|
||||
cy.fixture('users')
|
||||
.as('users');
|
||||
})
|
||||
it('Create users', () => {
|
||||
cy.get('@users')
|
||||
.then((users) => {
|
||||
users.forEach(user => {
|
||||
name = user.name
|
||||
console.log(user)
|
||||
cy.wait(1000)
|
||||
cy.contains("Add user").click()
|
||||
cy.get("input[name='mail']")
|
||||
.clear()
|
||||
.type(user.email)
|
||||
.should('have.value', user.email)
|
||||
cy.get("input[name='name']")
|
||||
.clear()
|
||||
.type(user.name)
|
||||
.should('have.value', user.name)
|
||||
cy.get("input[name='username']")
|
||||
.clear()
|
||||
.type(user.username)
|
||||
.should('have.value', user.username)
|
||||
cy.get("input[name='password']")
|
||||
.clear()
|
||||
.type(user.password)
|
||||
.should('have.value', user.password)
|
||||
cy.get("input[name='birthday']")
|
||||
.clear()
|
||||
.type(user.birthday)
|
||||
.should('have.value', user.birthday)
|
||||
if(user.foreman){
|
||||
cy.get("input[name='capo']")
|
||||
.check({force: true})
|
||||
}
|
||||
if(user.driver){
|
||||
cy.get("input[name='autista']")
|
||||
.check({force: true})
|
||||
}
|
||||
cy.contains("Submit").click()
|
||||
cy.contains(user.name)
|
||||
})
|
||||
})
|
||||
});
|
||||
})
|
|
@ -0,0 +1,37 @@
|
|||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
module.exports = (on, config) => {
|
||||
on('before:browser:launch', (browser = {}, launchOptions) => {
|
||||
console.log(launchOptions) // print all current args
|
||||
|
||||
if (browser.family === 'chromium' && browser.name !== 'electron') {
|
||||
launchOptions.preferences.default.intl = { accept_languages: "en" }
|
||||
}
|
||||
|
||||
if (browser.family === 'firefox') {
|
||||
launchOptions.preferences['intl.accept_languages'] = 'en'
|
||||
}
|
||||
|
||||
if (browser.name === 'electron') {
|
||||
launchOptions.args.push('--lang=en')
|
||||
launchOptions.preferences.darkTheme = true
|
||||
}
|
||||
|
||||
return launchOptions
|
||||
})
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
// ***********************************************
|
||||
// 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", (username="admin", password="correcthorsebatterystaple") => {
|
||||
cy.visit("/");
|
||||
cy.getCookie('acceptCookies')
|
||||
.then((c) => {
|
||||
if(c == undefined) cy.get(".acceptcookies").click({force: true})
|
||||
})
|
||||
cy.get("input[name='name']")
|
||||
.clear()
|
||||
.type(username)
|
||||
.should('have.value', username)
|
||||
|
||||
cy.get("input[name='password']")
|
||||
.clear()
|
||||
.type(password)
|
||||
.should('have.value', password)
|
||||
|
||||
cy.get("input[name='login']").click()
|
||||
})
|
||||
//
|
||||
//
|
||||
// -- 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 will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
|
@ -0,0 +1,28 @@
|
|||
// ***********************************************************
|
||||
// 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')
|
||||
|
||||
/*
|
||||
cy.server({
|
||||
onAnyRequest: (route, proxy) => {
|
||||
proxy.xhr.setRequestHeader('CUSTOM-HEADER', 'Header value')
|
||||
}
|
||||
})
|
||||
*/
|
|
@ -161,33 +161,36 @@ if(!is_cli()){
|
|||
var pwd = document.getElementById("pass1").value;
|
||||
result = zxcvbn(pwd);
|
||||
switch(result.score) {
|
||||
case 1:
|
||||
case 0:
|
||||
document.getElementById("pass1").className = "short";
|
||||
document.getElementById("pass-strength-result").className = "short";
|
||||
document.getElementById("pass-strength-result").innerHTML = "<?php t("Very weak"); ?>";
|
||||
break;
|
||||
case 2:
|
||||
case 1:
|
||||
document.getElementById("pass1").className = "bad";
|
||||
document.getElementById("pass-strength-result").className = "bad";
|
||||
document.getElementById("pass-strength-result").innerHTML = "<?php t("Weak"); ?>";
|
||||
break;
|
||||
case 3:
|
||||
case 2:
|
||||
document.getElementById("pass1").className = "good";
|
||||
document.getElementById("pass-strength-result").className = "good";
|
||||
document.getElementById("pass-strength-result").innerHTML = "<?php t("Good"); ?>";
|
||||
break;
|
||||
case 4:
|
||||
case 3:
|
||||
document.getElementById("pass1").className = "strong";
|
||||
document.getElementById("pass-strength-result").className = "strong";
|
||||
document.getElementById("pass-strength-result").innerHTML = "<?php t("Strong"); ?>";
|
||||
break;
|
||||
case 5:
|
||||
case 4:
|
||||
document.getElementById("pass1").className = "strong";
|
||||
document.getElementById("pass-strength-result").className = "strong";
|
||||
document.getElementById("pass-strength-result").innerHTML = "<?php t("Very strong"); ?>";
|
||||
break;
|
||||
default:
|
||||
// code block
|
||||
document.getElementById("pass1").className = "short";
|
||||
document.getElementById("pass-strength-result").className = "short";
|
||||
document.getElementById("pass-strength-result").innerHTML = "<?php t("Very weak"); ?>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -260,7 +263,7 @@ if(!is_cli()){
|
|||
<h1 class="screen-reader-text"><?php t("Installation completed successfully"); ?>.</h1>
|
||||
<p><?php t("Great job, man!"); echo(" "); t("You have completed the installation. Allerta can now function properly"); echo(".<br> "); t("If you are ready, it's time to..."); ?></p>
|
||||
<p class="step">
|
||||
<a href="../index.php"><?php t("Login"); ?></a>
|
||||
<a href="../index.php" class="login"><?php t("Login"); ?></a>
|
||||
</p>
|
||||
<?php
|
||||
unlink("runInstall.php");
|
||||
|
|
|
@ -23,7 +23,7 @@ return [
|
|||
"Your" => "Il tuo",
|
||||
"If %s doesn't work, you can get this information from your hosting provider" => "Se %s non funziona, richiedi queste informazioni al tuo fornitore di hosting",
|
||||
"Edit this item if you want to perform multiple Alert installations on a single database" => "Modifica questo campo se vuoi installare più volte Allerta sullo stesso database",
|
||||
"Error establishing a database connection" => "Errore nela connessione con il database",
|
||||
"Error establishing a database connection" => "Errore nella connessione con il database",
|
||||
"This could mean that %s and %s in file %s are wrong or that we cannot contact database %s. It could mean that your database is unreachable" => "Questo può significare che %s e %s nel file %s sono sbagliati o che il database %s non funziona. Può significare che il database non funziona",
|
||||
"Are you sure that %s and %s correct?" => "Sei sicuro che %s e %s siano corretti?",
|
||||
"Are you sure you have entered the correct hostname?" => "Sei sicuro che l'hostname sia corretto?",
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
|
||||
/**
|
||||
* Inherited Methods
|
||||
* @method void wantToTest($text)
|
||||
* @method void wantTo($text)
|
||||
* @method void execute($callable)
|
||||
* @method void expectTo($prediction)
|
||||
* @method void expect($prediction)
|
||||
* @method void amGoingTo($argumentation)
|
||||
* @method void am($role)
|
||||
* @method void lookForwardTo($achieveValue)
|
||||
* @method void comment($description)
|
||||
* @method void pause()
|
||||
*
|
||||
* @SuppressWarnings(PHPMD)
|
||||
*/
|
||||
class AcceptanceTester extends \Codeception\Actor
|
||||
{
|
||||
use _generated\AcceptanceTesterActions;
|
||||
|
||||
/**
|
||||
* Define custom actions here
|
||||
*/
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
<?php
|
||||
namespace Helper;
|
||||
|
||||
// here you can define custom actions
|
||||
// all public methods declared in helper class will be available in $I
|
||||
|
||||
class Acceptance extends \Codeception\Module
|
||||
{
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,24 +0,0 @@
|
|||
# Codeception Test Suite Configuration
|
||||
#
|
||||
# Suite for acceptance tests.
|
||||
# Perform tests in browser using the WebDriver or PhpBrowser.
|
||||
# If you need both WebDriver and PHPBrowser tests - create a separate suite.
|
||||
|
||||
actor: AcceptanceTester
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\PhpBuiltinServer
|
||||
config:
|
||||
Codeception\Extension\PhpBuiltinServer:
|
||||
hostname: localhost
|
||||
port: 8000
|
||||
autostart: true
|
||||
documentRoot: ../server/
|
||||
directoryIndex: index.php
|
||||
startDelay: 1
|
||||
modules:
|
||||
enabled:
|
||||
- PhpBrowser:
|
||||
url: http://localhost:8000/
|
||||
- \Helper\Acceptance
|
||||
step_decorators: ~
|
|
@ -1,97 +0,0 @@
|
|||
<?php
|
||||
|
||||
class FirstCest
|
||||
{
|
||||
public function installWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->haveServerParameter('HTTP_ACCEPT_LANGUAGE', 'en-US;q=0.5,en;q=0.3');
|
||||
$I->amOnPage('/install/install.php');
|
||||
$I->click('Submit');
|
||||
$I->seeCurrentURLEquals('/install/install.php');
|
||||
$I->fillField('dbhost', '127.0.0.1');
|
||||
$I->fillField('uname', 'root');
|
||||
$I->fillField('pwd', 'password');
|
||||
$I->click('Submit');
|
||||
|
||||
$I->click('Populate DB');
|
||||
|
||||
$I->fillField('user_name', 'admin');
|
||||
$I->fillField('admin_password', 'password');
|
||||
$I->checkOption('admin_visible');
|
||||
$I->fillField('admin_email', 'allerta@example.com');
|
||||
$I->fillField('owner', 'owner');
|
||||
$I->click('Install Allerta');
|
||||
$I->see('Login');
|
||||
$I->click('Login');
|
||||
$I->fillField('name', 'admin');
|
||||
$I->fillField('password', 'password');
|
||||
$I->click('Login');
|
||||
$I->seeCurrentURLEquals('/list.php');
|
||||
$I->see('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends installWorks
|
||||
*/
|
||||
public function logsWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage('/list.php');
|
||||
$I->click('Active');
|
||||
$I->click('Log');
|
||||
$I->seeCurrentURLEquals('/log.php');
|
||||
$I->see('Attivazione disponibilita\'');
|
||||
|
||||
$I->click('Lista Disponibilità');
|
||||
$I->seeCurrentURLEquals('/list.php');
|
||||
$I->click('Not Active');
|
||||
$I->seeCurrentURLEquals('/list.php');
|
||||
$I->click('Log');
|
||||
$I->seeCurrentURLEquals('/log.php');
|
||||
$I->see('Rimozione disponibilita\'');
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends installWorks
|
||||
*/
|
||||
public function addUsersWorks(AcceptanceTester $I)
|
||||
{
|
||||
$I->amOnPage('/list.php');
|
||||
$I->click('Add user');
|
||||
$I->seeCurrentURLEquals('/edit_user.php?add');
|
||||
/* TODO
|
||||
$I->click('Lista Disponibilità');
|
||||
$I->seeCurrentURLEquals('/list.php');
|
||||
$I->click('Not Active');
|
||||
$I->seeCurrentURLEquals('/list.php');
|
||||
$I->click('Log');
|
||||
$I->seeCurrentURLEquals('/log.php');
|
||||
$I->see('Rimozione disponibilita\'');
|
||||
*/
|
||||
}
|
||||
|
||||
//public function servicesWorks(AcceptanceTester $I)
|
||||
//{
|
||||
/**
|
||||
* @var FakerGenerator
|
||||
*/
|
||||
/* TODO: Add more users
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$I->amOnPage('/list.php');
|
||||
$I->click('Services');
|
||||
$I->seeCurrentURLEquals('/services.php');
|
||||
$I->click('add service');
|
||||
$I->seeCurrentURLEquals('/edit_service.php');
|
||||
$I->fillField('data', '2020-01-01');
|
||||
$I->fillField('uscita', '12:12');
|
||||
$I->fillField('rientro', '14:14');
|
||||
//TODO: check options
|
||||
$I->type('luogo', $faker->word);
|
||||
$I->type('note', $faker->word);
|
||||
$I->click('invia');
|
||||
$I->seeCurrentURLEquals('/services.php');
|
||||
$I->see('type2');
|
||||
*/
|
||||
//}
|
||||
}
|
||||
|
Loading…
Reference in New Issue