begin setup automation with deno and playwright
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.terraform*
|
||||
.DS_Store
|
||||
.vscode
|
||||
.vscode
|
||||
.env
|
||||
3
ansible/arcodange/erp/README.md
Normal file
3
ansible/arcodange/erp/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Ansible Collection - arcodange.erp
|
||||
|
||||
Documentation for the collection.
|
||||
69
ansible/arcodange/erp/galaxy.yml
Normal file
69
ansible/arcodange/erp/galaxy.yml
Normal file
@@ -0,0 +1,69 @@
|
||||
### REQUIRED
|
||||
# The namespace of the collection. This can be a company/brand/organization or product namespace under which all
|
||||
# content lives. May only contain alphanumeric lowercase characters and underscores. Namespaces cannot start with
|
||||
# underscores or numbers and cannot contain consecutive underscores
|
||||
namespace: arcodange
|
||||
|
||||
# The name of the collection. Has the same character restrictions as 'namespace'
|
||||
name: erp
|
||||
|
||||
# The version of the collection. Must be compatible with semantic versioning
|
||||
version: 1.0.0
|
||||
|
||||
# The path to the Markdown (.md) readme file. This path is relative to the root of the collection
|
||||
readme: README.md
|
||||
|
||||
# A list of the collection's content authors. Can be just the name or in the format 'Full Name <email> (url)
|
||||
# @nicks:irc/im.site#channel'
|
||||
authors:
|
||||
- your name <example@domain.com>
|
||||
|
||||
|
||||
### OPTIONAL but strongly recommended
|
||||
# A short summary description of the collection
|
||||
description: your collection description
|
||||
|
||||
# Either a single license or a list of licenses for content inside of a collection. Ansible Galaxy currently only
|
||||
# accepts L(SPDX,https://spdx.org/licenses/) licenses. This key is mutually exclusive with 'license_file'
|
||||
license:
|
||||
- GPL-2.0-or-later
|
||||
|
||||
# The path to the license file for the collection. This path is relative to the root of the collection. This key is
|
||||
# mutually exclusive with 'license'
|
||||
license_file: ''
|
||||
|
||||
# A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character
|
||||
# requirements as 'namespace' and 'name'
|
||||
tags: []
|
||||
|
||||
# Collections that this collection requires to be installed for it to be usable. The key of the dict is the
|
||||
# collection label 'namespace.name'. The value is a version range
|
||||
# L(specifiers,https://python-semanticversion.readthedocs.io/en/latest/#requirement-specification). Multiple version
|
||||
# range specifiers can be set and are separated by ','
|
||||
dependencies: {}
|
||||
|
||||
# The URL of the originating SCM repository
|
||||
repository: http://example.com/repository
|
||||
|
||||
# The URL to any online docs
|
||||
documentation: http://docs.example.com
|
||||
|
||||
# The URL to the homepage of the collection/project
|
||||
homepage: http://example.com
|
||||
|
||||
# The URL to the collection issue tracker
|
||||
issues: http://example.com/issue/tracker
|
||||
|
||||
# A list of file glob-like patterns used to filter any files or directories that should not be included in the build
|
||||
# artifact. A pattern is matched from the relative path of the file or directory of the collection directory. This
|
||||
# uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry',
|
||||
# and '.git' are always filtered. Mutually exclusive with 'manifest'
|
||||
build_ignore: []
|
||||
|
||||
# A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a
|
||||
# list of MANIFEST.in style
|
||||
# L(directives,https://packaging.python.org/en/latest/guides/using-manifest-in/#manifest-in-commands). The key
|
||||
# 'omit_default_directives' is a boolean that controls whether the default directives are used. Mutually exclusive
|
||||
# with 'build_ignore'
|
||||
# manifest: null
|
||||
|
||||
52
ansible/arcodange/erp/meta/runtime.yml
Normal file
52
ansible/arcodange/erp/meta/runtime.yml
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
# Collections must specify a minimum required ansible version to upload
|
||||
# to galaxy
|
||||
# requires_ansible: '>=2.9.10'
|
||||
|
||||
# Content that Ansible needs to load from another location or that has
|
||||
# been deprecated/removed
|
||||
# plugin_routing:
|
||||
# action:
|
||||
# redirected_plugin_name:
|
||||
# redirect: ns.col.new_location
|
||||
# deprecated_plugin_name:
|
||||
# deprecation:
|
||||
# removal_version: "4.0.0"
|
||||
# warning_text: |
|
||||
# See the porting guide on how to update your playbook to
|
||||
# use ns.col.another_plugin instead.
|
||||
# removed_plugin_name:
|
||||
# tombstone:
|
||||
# removal_version: "2.0.0"
|
||||
# warning_text: |
|
||||
# See the porting guide on how to update your playbook to
|
||||
# use ns.col.another_plugin instead.
|
||||
# become:
|
||||
# cache:
|
||||
# callback:
|
||||
# cliconf:
|
||||
# connection:
|
||||
# doc_fragments:
|
||||
# filter:
|
||||
# httpapi:
|
||||
# inventory:
|
||||
# lookup:
|
||||
# module_utils:
|
||||
# modules:
|
||||
# netconf:
|
||||
# shell:
|
||||
# strategy:
|
||||
# terminal:
|
||||
# test:
|
||||
# vars:
|
||||
|
||||
# Python import statements that Ansible needs to load from another location
|
||||
# import_redirection:
|
||||
# ansible_collections.ns.col.plugins.module_utils.old_location:
|
||||
# redirect: ansible_collections.ns.col.plugins.module_utils.new_location
|
||||
|
||||
# Groups of actions/modules that take a common set of options
|
||||
# action_groups:
|
||||
# group_name:
|
||||
# - module1
|
||||
# - module2
|
||||
31
ansible/arcodange/erp/plugins/README.md
Normal file
31
ansible/arcodange/erp/plugins/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Collections Plugins Directory
|
||||
|
||||
This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that
|
||||
is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that
|
||||
would contain module utils and modules respectively.
|
||||
|
||||
Here is an example directory of the majority of plugins currently supported by Ansible:
|
||||
|
||||
```
|
||||
└── plugins
|
||||
├── action
|
||||
├── become
|
||||
├── cache
|
||||
├── callback
|
||||
├── cliconf
|
||||
├── connection
|
||||
├── filter
|
||||
├── httpapi
|
||||
├── inventory
|
||||
├── lookup
|
||||
├── module_utils
|
||||
├── modules
|
||||
├── netconf
|
||||
├── shell
|
||||
├── strategy
|
||||
├── terminal
|
||||
├── test
|
||||
└── vars
|
||||
```
|
||||
|
||||
A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.17/plugins/plugins.html).
|
||||
7
ansible/requirements.yml
Normal file
7
ansible/requirements.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
roles: []
|
||||
|
||||
collections: []
|
||||
# - name: community.general
|
||||
# - name: kubernetes.core
|
||||
# - name: git+https://github.com/k3s-io/k3s-ansible.git
|
||||
26
static/config/company.json
Normal file
26
static/config/company.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"info": {
|
||||
"raisonSociale": "Arcodange",
|
||||
"adresse": "73 Boulevard de l yerres",
|
||||
"codePostal": "91000",
|
||||
"ville": "Evry Courcouronnes",
|
||||
"pays": "France (FR)",
|
||||
"telephonePortable": "0664574539",
|
||||
"siteWeb": "gitea.arcodange.duckdns.org",
|
||||
"email": "arcodange@gmail.com",
|
||||
"logoFilePath": "$IMG/logo512.png"
|
||||
},
|
||||
"ID": {
|
||||
"directeurs": "M. Radureau Gabriel",
|
||||
"responsableRGPD": "M. Radureau Gabriel",
|
||||
"formeJuridique": "Société par actions simplifiée (SAS)",
|
||||
"naf_ape": "62.02A",
|
||||
"objetDeLaSociete": "La conception,l\"installation, l\"adaptation d\"applications informatiques . La prestation de service, l\"étude et le conseil dans les domaines technologiques et informatiques. L\"accompagnement et la formation des clients aux nouvelles techniques, outils et langages informatiques etc...",
|
||||
"siren": "123456789",
|
||||
"siret": "12345678900011",
|
||||
"capital": "1000€",
|
||||
"numTva": "FR00000000000",
|
||||
"rcs_rm": "000 000 000 R.C.S. Evry",
|
||||
"moisDebutExercice": "Juillet"
|
||||
}
|
||||
}
|
||||
BIN
static/img/loginBackground.jpeg
Normal file
BIN
static/img/loginBackground.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
BIN
static/img/logo512.png
Normal file
BIN
static/img/logo512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 536 KiB |
5
test/.env.example
Normal file
5
test/.env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
DOLIBARR_ADDRESS=https://erp.arcodange.duckdns.org
|
||||
DOLI_DB_PASSWORD=
|
||||
DOLI_ADMIN_LOGIN=admin
|
||||
DOLI_ADMIN_PASSWORD=""
|
||||
ROOT_FOLDER=$HOME/erp
|
||||
7
test/deno.json
Normal file
7
test/deno.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"checkJs": true,
|
||||
"imports": {
|
||||
"playwright": "npm:playwright@^1.48.2",
|
||||
"load_dotenv": "jsr:@std/dotenv/load"
|
||||
}
|
||||
}
|
||||
33
test/deno.lock
generated
Normal file
33
test/deno.lock
generated
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"version": "4",
|
||||
"specifiers": {
|
||||
"jsr:@std/dotenv@*": "0.225.2",
|
||||
"npm:playwright@^1.48.2": "1.48.2"
|
||||
},
|
||||
"jsr": {
|
||||
"@std/dotenv@0.225.2": {
|
||||
"integrity": "e2025dce4de6c7bca21dece8baddd4262b09d5187217e231b033e088e0c4dd23"
|
||||
}
|
||||
},
|
||||
"npm": {
|
||||
"fsevents@2.3.2": {
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="
|
||||
},
|
||||
"playwright-core@1.48.2": {
|
||||
"integrity": "sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA=="
|
||||
},
|
||||
"playwright@1.48.2": {
|
||||
"integrity": "sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==",
|
||||
"dependencies": [
|
||||
"fsevents",
|
||||
"playwright-core"
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"jsr:@std/dotenv@*",
|
||||
"npm:playwright@^1.48.2"
|
||||
]
|
||||
}
|
||||
}
|
||||
77
test/main.ts
Normal file
77
test/main.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import "load_dotenv";
|
||||
import { chromium } from "playwright";
|
||||
import initialSetup from "./scripts/admin/initialSetup.ts";
|
||||
import login from "./scripts/login.ts";
|
||||
import companySetup, { Company } from "./scripts/admin/companySetup.ts";
|
||||
import path from "node:path";
|
||||
import displaySetup from "./scripts/admin/displaySetup.ts";
|
||||
|
||||
/*
|
||||
Initialisation
|
||||
*/
|
||||
const dolibarrAddress = Deno.env.get("DOLIBARR_ADDRESS") ||
|
||||
"https://erp.arcodange.duckdns.org";
|
||||
const debug = true;
|
||||
const DBpassword = Deno.env.get("DOLI_DB_PASSWORD") || "undefined";
|
||||
const adminCredentials = {
|
||||
username: Deno.env.get("DOLI_ADMIN_LOGIN") || "undefined",
|
||||
password: Deno.env.get("DOLI_ADMIN_PASSWORD") || "undefined",
|
||||
};
|
||||
|
||||
const rootFolderPath = Deno.env.get("ROOT_FOLDER") ||
|
||||
"/Users/gabrielradureau/Desktop/ARCODANGE/erp";
|
||||
const imgFolderPath = Deno.env.get("IMG_FOLDER") ||
|
||||
path.join(rootFolderPath, "static/img");
|
||||
const configFolderPath = Deno.env.get("CONFIG_FOLDER") ||
|
||||
path.join(rootFolderPath, "static/config");
|
||||
|
||||
const browser = await chromium.launch({
|
||||
headless: false,
|
||||
logger: {
|
||||
isEnabled: (_name, _severity) => debug,
|
||||
log: (name, severity, message, args) =>
|
||||
console.warn(`${severity}| ${name} :: ${message} __ ${args}`),
|
||||
},
|
||||
});
|
||||
const context = await browser.newContext({ locale: "fr-FR" });
|
||||
const page = await context.newPage();
|
||||
|
||||
const globalCtx = {
|
||||
dolibarrAddress,
|
||||
DBpassword,
|
||||
adminCredentials,
|
||||
|
||||
debug,
|
||||
rootFolderPath,
|
||||
imgFolderPath,
|
||||
configFolderPath,
|
||||
|
||||
browser,
|
||||
page,
|
||||
context,
|
||||
};
|
||||
|
||||
// if (!await initialSetup.isUpgradeLocked(globalCtx)) {
|
||||
// await initialSetup.doFirstInstall(globalCtx);
|
||||
// } else {
|
||||
// console.log("Installation et Mises à jours bloquée par fichier .lock.");
|
||||
// }
|
||||
|
||||
// await login.doAdminLogin(globalCtx);
|
||||
// console.log(`connected as ${await login.whoAmI(globalCtx)}`);
|
||||
|
||||
// const company = JSON.parse(
|
||||
// await Deno.readTextFile(
|
||||
// path.join(globalCtx.configFolderPath, "company.json"),
|
||||
// ),
|
||||
// ) as Company;
|
||||
|
||||
// await companySetup.setupCompany(globalCtx, company);
|
||||
|
||||
try {
|
||||
await displaySetup.setupDisplay(globalCtx);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// await browser.close();
|
||||
BIN
test/save.png
Normal file
BIN
test/save.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
97
test/scripts/admin/companySetup.ts
Normal file
97
test/scripts/admin/companySetup.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
docker run -v ./script.js:/app/script.js --rm playwright:1.47.0
|
||||
*/
|
||||
import type { Page } from "playwright";
|
||||
import forms from "../forms.ts";
|
||||
|
||||
export type CompanyInfo = {
|
||||
raisonSociale: string;
|
||||
adresse: string;
|
||||
codePostal: string;
|
||||
ville: string;
|
||||
pays: string; // France (FR)
|
||||
telephoneFixe?: string;
|
||||
telephonePortable?: string;
|
||||
email: string;
|
||||
siteWeb: string;
|
||||
logoFilePath: string;
|
||||
};
|
||||
|
||||
export type CompanyID = {
|
||||
directeurs: string; // Nom du(des) gestionnaire(s) (PDG, directeur, président...)
|
||||
responsableRGPD: string; // Délégué à la protection des données (DPO, contact RGPD, ...)
|
||||
capital: string; // Capital
|
||||
formeJuridique: string; // Type d'entité légale (SASU,...)
|
||||
siren: string; // SIREN
|
||||
siret: string; // SIRET
|
||||
naf_ape: string; // NAF-APE
|
||||
rcs_rm: string; // RCS/RM (RNE)
|
||||
numEori?: string; // numéro EORI (douanes)
|
||||
numRna?: string; // numéro RNA (associations)
|
||||
numTva: string; // Numéro de TVA
|
||||
objetDeLaSociete: string; // Objet de la société
|
||||
moisDebutExercice: string; // Mois de début d'exercice
|
||||
};
|
||||
|
||||
export type Company = {
|
||||
info: CompanyInfo;
|
||||
ID: CompanyID;
|
||||
};
|
||||
|
||||
const companyInfoDolibarrInputNames = new Map(Object.entries({
|
||||
raisonSociale: "name",
|
||||
adresse: "MAIN_INFO_SOCIETE_ADDRESS",
|
||||
codePostal: "MAIN_INFO_SOCIETE_ZIP",
|
||||
ville: "MAIN_INFO_SOCIETE_TOWN",
|
||||
pays: "country_id:select",
|
||||
telephoneFixe: "phone",
|
||||
telephonePortable: "phone_mobile",
|
||||
email: "mail",
|
||||
siteWeb: "web",
|
||||
logoFilePath: "logo_squarred:file",
|
||||
})) as Map<keyof CompanyInfo, string>;
|
||||
|
||||
const companyIDDolibarrInputNames = new Map(Object.entries({
|
||||
directeurs: "MAIN_INFO_SOCIETE_MANAGERS",
|
||||
responsableRGPD: "MAIN_INFO_GDPR",
|
||||
capital: "capital",
|
||||
formeJuridique: "forme_juridique_code:select",
|
||||
siren: "siren",
|
||||
siret: "siret",
|
||||
naf_ape: "ape",
|
||||
rcs_rm: "rcs",
|
||||
numEori: "MAIN_INFO_PROFID5",
|
||||
numRna: "MAIN_INFO_PROFID6",
|
||||
numTva: "tva",
|
||||
objetDeLaSociete: "socialobject",
|
||||
moisDebutExercice: "SOCIETE_FISCAL_MONTH_START:select",
|
||||
})) as Map<keyof CompanyID, string>;
|
||||
|
||||
async function setupCompany(
|
||||
{ imgFolderPath, page, dolibarrAddress }: {
|
||||
imgFolderPath: string;
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
},
|
||||
company: Company,
|
||||
): Promise<void> {
|
||||
await page.goto(`${dolibarrAddress}/admin/company.php`);
|
||||
|
||||
await forms.fillForm(
|
||||
{ page, imgFolderPath },
|
||||
company.info,
|
||||
companyInfoDolibarrInputNames,
|
||||
1,
|
||||
);
|
||||
|
||||
await forms.fillForm(
|
||||
{ page, imgFolderPath },
|
||||
company.ID,
|
||||
companyIDDolibarrInputNames,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
setupCompany,
|
||||
};
|
||||
94
test/scripts/admin/displaySetup.ts
Normal file
94
test/scripts/admin/displaySetup.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
docker run -v ./script.js:/app/script.js --rm playwright:1.47.0
|
||||
*/
|
||||
import type { Page } from "playwright";
|
||||
import login from "../login.ts";
|
||||
import forms from "../forms.ts";
|
||||
|
||||
async function removeLoginForgottenPasswordLink(
|
||||
{ page, dolibarrAddress }: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
imgFolderPath: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
await page.goto(`${dolibarrAddress}/admin/security.php`);
|
||||
const label = `Cacher le lien "Mot de passe oublié" sur la page de connexion`;
|
||||
const row = page.locator("tr").filter({
|
||||
has: page.locator("td", { hasText: label }),
|
||||
});
|
||||
|
||||
const enableLink = row.locator("a", {
|
||||
hasText: /^Activer$/,
|
||||
hasNotText: /^Désactiver$/,
|
||||
});
|
||||
const disableLink = row.locator("a", {
|
||||
hasText: /^Désactiver$/,
|
||||
hasNotText: /^Activer$/,
|
||||
});
|
||||
if (await enableLink.isVisible()) {
|
||||
await enableLink.click();
|
||||
} else if (!await disableLink.isVisible()) {
|
||||
throw new Error("Enable/Disable link not found");
|
||||
}
|
||||
}
|
||||
async function setupLoginPage(
|
||||
globalCtx: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
imgFolderPath: string;
|
||||
},
|
||||
{ removeHelpLink, HTMLMessage, backgroundImage }: {
|
||||
removeHelpLink?: boolean;
|
||||
HTMLMessage?: string;
|
||||
backgroundImage?: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
const { page, dolibarrAddress } = globalCtx;
|
||||
await page.goto(`${dolibarrAddress}/admin/ihm.php?mode=login`);
|
||||
|
||||
await forms.fillForm(
|
||||
globalCtx,
|
||||
{
|
||||
HTMLMessage,
|
||||
backgroundImage,
|
||||
removeHelpLink: (removeHelpLink||false).toString(),
|
||||
},
|
||||
new Map([
|
||||
["removeHelpLink",`Cacher le lien «Besoin d'aide ou assistance» sur la page de connexion:toggle`],
|
||||
["backgroundImage","imagebackground:file"],
|
||||
["HTMLMessage", "main_home:cke"]
|
||||
]),
|
||||
1,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
async function setupDisplay(
|
||||
globalCtx: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
adminCredentials: { username: string; password: string };
|
||||
imgFolderPath: string;
|
||||
},
|
||||
): Promise<void> {
|
||||
await login.doAdminLogin(globalCtx);
|
||||
await removeLoginForgottenPasswordLink(globalCtx);
|
||||
await setupLoginPage(globalCtx, {
|
||||
removeHelpLink: true,
|
||||
HTMLMessage: `
|
||||
<div style="text-align:center"><span style="color:#000000">Bienvenue chez </span><span style="color:#2c3e50"><em>Gabriel Radureau</em></span><br />
|
||||
<span style="color:#000000"><em>l'</em></span><span style="color:#e74c3c"><em>ar</em></span><span style="color:#c0392b"><em>change</em></span><span style="color:#000000"><em> du </em></span><span style="color:#2ecc71"><em>code</em></span><br />
|
||||
<span style="color:#000000">🏹💻🪽<br />
|
||||
<em><span style="font-size:8px">Pariez sur le bon sagitaire</span></em></span></div>
|
||||
`.trim(),
|
||||
backgroundImage: "$IMG/loginBackground.jpeg",
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
setupDisplay,
|
||||
|
||||
removeLoginForgottenPasswordLink,
|
||||
setupLoginPage,
|
||||
};
|
||||
63
test/scripts/admin/initialSetup.ts
Normal file
63
test/scripts/admin/initialSetup.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
docker run -v ./script.js:/app/script.js --rm playwright:1.47.0
|
||||
*/
|
||||
import type { Page } from "playwright";
|
||||
|
||||
interface credentials {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
async function goToInstallationPage(
|
||||
{ page, dolibarrAddress }: { page: Page; dolibarrAddress: string },
|
||||
): Promise<void> {
|
||||
await page.goto(`${dolibarrAddress}/install/`);
|
||||
}
|
||||
|
||||
async function doFirstInstall(
|
||||
{ page, dolibarrAddress, DBpassword, adminCredentials }: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
DBpassword: string;
|
||||
adminCredentials: credentials;
|
||||
},
|
||||
): Promise<void> {
|
||||
await goToInstallationPage({ page, dolibarrAddress });
|
||||
|
||||
const beginFirstInstallBtn = page.locator(
|
||||
"table table table tr:last-of-type a.button",
|
||||
);
|
||||
await beginFirstInstallBtn.click();
|
||||
|
||||
// 1
|
||||
await page.fill('input[name="db_pass"]', DBpassword);
|
||||
await page.click('input[type="submit"]');
|
||||
|
||||
// 2 Migrations
|
||||
await page.click('input[type="submit"]', { timeout: 3600 * 1000 });
|
||||
|
||||
// 3
|
||||
await page.uncheck('input[name="dolibarrpingno"]');
|
||||
await page.click('input[type="submit"]');
|
||||
|
||||
await page.fill('input[name="login"]', adminCredentials.username);
|
||||
await page.fill('input[name="pass"]', adminCredentials.password);
|
||||
await page.fill('input[name="pass_verif"]', adminCredentials.password);
|
||||
await page.click('input[type="submit"]');
|
||||
}
|
||||
|
||||
async function isUpgradeLocked(
|
||||
{ page, dolibarrAddress }: { page: Page; dolibarrAddress: string },
|
||||
): Promise<boolean> {
|
||||
await goToInstallationPage({ page, dolibarrAddress });
|
||||
return (
|
||||
await page.locator("table").count() === 0 &&
|
||||
await page.getByText("Cliquez ici pour aller sur votre application")
|
||||
.count() > 0
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
doFirstInstall,
|
||||
isUpgradeLocked,
|
||||
};
|
||||
75
test/scripts/forms.ts
Normal file
75
test/scripts/forms.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { Page } from "playwright";
|
||||
|
||||
async function toggleOnOff({page}:{page:Page}, label: string, on: boolean) : Promise<void>{
|
||||
|
||||
const row = page.locator("tr").filter({
|
||||
has: page.locator("td", { hasText: label }),
|
||||
});
|
||||
const hideBtn = row.locator(`[title="Désactivé"]`);
|
||||
const showBtn = row.locator(`[title="Actif"]`);
|
||||
if (!await hideBtn.isVisible() && !await showBtn.isVisible()) {
|
||||
throw new Error("Show/Hide button not found");
|
||||
}
|
||||
if (on && await hideBtn.isVisible()) await hideBtn.click();
|
||||
if (!on && await showBtn.isVisible()) await showBtn.click();
|
||||
}
|
||||
|
||||
// Fonction générique `fillForm` pour remplir des formulaires
|
||||
async function fillForm<T>(
|
||||
{ page, imgFolderPath }: { page: Page; imgFolderPath: string },
|
||||
data: T,
|
||||
inputNames: Map<keyof T, string>,
|
||||
submit: number = 0,
|
||||
) {
|
||||
for (const [attr, input] of inputNames) {
|
||||
const attrValue = data[attr];
|
||||
if (!attrValue) continue;
|
||||
|
||||
const [inputName, inputType] = input.split(":");
|
||||
console.log(`${inputName} => '${attrValue}'`);
|
||||
switch (inputType) {
|
||||
case "select":
|
||||
await page.selectOption(`select[name="${inputName}"]`, {
|
||||
label: attrValue as string,
|
||||
});
|
||||
break;
|
||||
case "file": {
|
||||
const [fileChooser] = await Promise.all([
|
||||
page.waitForEvent("filechooser"),
|
||||
page.click(`input[name="${inputName}"]`),
|
||||
]);
|
||||
await fileChooser.setFiles(
|
||||
(attrValue as string).replace("$IMG", imgFolderPath),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "cke": { // CKEditor https://ckeditor.com/wysiwyg-editor-open-source/
|
||||
const col = page.locator("td", {has: page.locator(`textarea[name="${inputName}"]`)});
|
||||
const htmlEditor = col.frameLocator('iframe').locator('body');
|
||||
const rawHTMLEditor = col.locator('textarea.cke_editable');
|
||||
|
||||
const htmlToggleBtn = col.getByRole("button").filter({hasText: /Source/});
|
||||
await htmlToggleBtn.click();
|
||||
await rawHTMLEditor.fill(attrValue as string);
|
||||
await htmlToggleBtn.click();
|
||||
await htmlEditor.screenshot({path: 'save.png'});
|
||||
break;
|
||||
}
|
||||
case "toggle":
|
||||
await toggleOnOff({page}, inputName, Boolean(JSON.parse(attrValue as string)))
|
||||
break;
|
||||
default:
|
||||
await page.fill(
|
||||
`input[name="${inputName}"],textarea[name="${inputName}"]`,
|
||||
attrValue as string,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (submit > 0) {
|
||||
await page.click(`:nth-match(input[type="submit"], ${submit})`);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
fillForm,
|
||||
};
|
||||
64
test/scripts/login.ts
Normal file
64
test/scripts/login.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { Page } from "playwright";
|
||||
|
||||
interface credentials {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
async function whoAmI({ page }: {
|
||||
page: Page;
|
||||
}): Promise<string> {
|
||||
if (await isLogOut({ page })) return "";
|
||||
return (await page.locator('.login_block_user a[data-toggle="dropdown"]')
|
||||
.textContent())?.trim() || "";
|
||||
}
|
||||
|
||||
async function isLogOut({ page }: {
|
||||
page: Page;
|
||||
}): Promise<boolean> {
|
||||
return (
|
||||
await page.locator(".login_block").count() === 0 &&
|
||||
await page.locator(".login_table").count() > 0
|
||||
);
|
||||
}
|
||||
|
||||
async function doLogout({ page }: {
|
||||
page: Page;
|
||||
}): Promise<void> {
|
||||
if (await isLogOut({ page })) return;
|
||||
await page.locator(".login_block .dropdown").click();
|
||||
await page.locator("i.fa-sign-out-alt").click();
|
||||
}
|
||||
|
||||
async function doLogin(
|
||||
{ page, dolibarrAddress }: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
},
|
||||
{ username, password }: credentials,
|
||||
): Promise<void> {
|
||||
await page.goto(`${dolibarrAddress}/`);
|
||||
|
||||
if (await whoAmI({ page }) === username) return;
|
||||
await doLogout({ page });
|
||||
|
||||
await page.fill('input[name="username"]', username);
|
||||
await page.fill('input[name="password"]', password);
|
||||
await page.click('input[type="submit"]');
|
||||
}
|
||||
|
||||
async function doAdminLogin({ page, dolibarrAddress, adminCredentials }: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
adminCredentials: credentials;
|
||||
}) {
|
||||
await doLogin({ page, dolibarrAddress }, adminCredentials);
|
||||
}
|
||||
|
||||
export default {
|
||||
doAdminLogin,
|
||||
doLogin,
|
||||
doLogout,
|
||||
isLogOut,
|
||||
whoAmI,
|
||||
};
|
||||
Reference in New Issue
Block a user