feat(test): provision erp-sandbox via Playwright (REST API + write-scoped ai_agent_sandbox user)
Extend the Deno + Playwright UI-automation POC to provision the erp-sandbox Dolibarr for the AI agent: - moduleSetup.ts: add enableApiModule(ctx) — toggles the REST API / Web services module on /admin/modules.php (kanban). Resilient: tries the fr_FR card label "API/Web services REST (serveur)" first, falls back to a /API.*REST|REST.*API/i title match if the exact label is absent. - userSetup.ts (new): createUser (returns the new numeric id), assignRights (clicks each addrights link on /user/perms.php, idempotent), generateApiKey (triggers Dolibarr's generate control on the user card and reads the value back). - provisionSandbox.ts (new entrypoint, main.ts untouched): login → enable API → create ai_agent_sandbox (non-admin) → grant write rights → generate API key, then write the key to test/.ai_agent_sandbox.key (gitignored) instead of printing it. - .gitignore (new), .env.example + README.md: sandbox vars, the deno run --allow-all provisionSandbox.ts command, and kubectl one-liners to pull DOLI_ADMIN_PASSWORD (secretkv) / DOLI_DB_PASSWORD (vso-db-credentials) from the erp-sandbox namespace. Why UI not SQL: API keys are encrypted with the instance's DOLI_INSTANCE_UNIQUE_ID, so the key must be generated by the sandbox itself, not INSERTed raw. deno check passes for provisionSandbox.ts and scripts/admin/userSetup.ts. NOT run end-to-end: the sandbox Dolibarr is not installed yet (empty DB / fresh install wizard), so the selectors are best-effort Dolibarr 22 conventions and must be confirmed on the first real run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,19 +1,20 @@
|
||||
|
||||
// info-box
|
||||
|
||||
import { Page } from "playwright";
|
||||
import { Locator, Page } from "playwright";
|
||||
import forms from "../forms.ts";
|
||||
import login from "../login.ts";
|
||||
|
||||
// span.info-box-title has-text /moduleName/
|
||||
// span.fa-cog[title="Configuration"]
|
||||
|
||||
type ModuleCtx = {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
adminCredentials: { username: string; password: string };
|
||||
};
|
||||
|
||||
async function configureModule(
|
||||
globalCtx: {
|
||||
page: Page;
|
||||
dolibarrAddress: string;
|
||||
adminCredentials: { username: string; password: string };
|
||||
},
|
||||
globalCtx: ModuleCtx,
|
||||
{
|
||||
moduleName,
|
||||
enabled,
|
||||
@@ -28,7 +29,7 @@ async function configureModule(
|
||||
await login.doAdminLogin(globalCtx);
|
||||
|
||||
await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`);
|
||||
|
||||
|
||||
const moduleDiv = page.locator('.info-box', {
|
||||
has: page.locator('.info-box-title',{
|
||||
hasText: new RegExp('^'+moduleName+'\n?$','i')
|
||||
@@ -42,6 +43,61 @@ async function configureModule(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Enable Dolibarr's REST API / Web services module.
|
||||
|
||||
Activation maps to llx_const.MAIN_MODULE_API=1; in the UI this is the module
|
||||
card on /admin/modules.php (kanban mode). In fr_FR the card title is
|
||||
"API/Web services REST (serveur)" (family "Interfaces").
|
||||
|
||||
NOTE: confirm the exact card title on the first real run against an installed
|
||||
sandbox — the label has not been verified live here. To stay resilient we try
|
||||
the known fr_FR label first (anchored exact match, same as configureModule),
|
||||
and if that card is not present we fall back to matching ANY module card whose
|
||||
title contains "API ... REST" / "REST ... API" in either order.
|
||||
*/
|
||||
async function enableApiModule(globalCtx: ModuleCtx): Promise<void> {
|
||||
const {page, dolibarrAddress}= globalCtx;
|
||||
|
||||
// fr_FR label as observed in the module catalogue. Confirm on first real run.
|
||||
const knownLabel = "API/Web services REST (serveur)";
|
||||
|
||||
await login.doAdminLogin(globalCtx);
|
||||
await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`);
|
||||
|
||||
const exactCard = page.locator('.info-box', {
|
||||
has: page.locator('.info-box-title',{
|
||||
hasText: new RegExp('^'+knownLabel+'\n?$','i')
|
||||
})
|
||||
});
|
||||
|
||||
if(await exactCard.count() > 0) {
|
||||
await forms.toggleOnOff(exactCard, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: the exact fr_FR label was not found (different Dolibarr version,
|
||||
// locale, or wording). Match any card whose title looks like the REST API
|
||||
// module regardless of word order.
|
||||
const fuzzyCard = page.locator('.info-box', {
|
||||
has: page.locator('.info-box-title',{
|
||||
hasText: /API.*REST|REST.*API/i
|
||||
})
|
||||
});
|
||||
|
||||
if(await fuzzyCard.count() === 0) {
|
||||
throw new Error(
|
||||
`REST API module card not found on ${dolibarrAddress}/admin/modules.php `+
|
||||
`(tried exact label "${knownLabel}" and /API.*REST|REST.*API/i). `+
|
||||
`Confirm the card title in the running sandbox.`,
|
||||
);
|
||||
}
|
||||
|
||||
// If several cards match the fuzzy pattern, toggle the first one only.
|
||||
await forms.toggleOnOff(fuzzyCard.first() as Locator, true);
|
||||
}
|
||||
|
||||
export default {
|
||||
configureModule,
|
||||
enableApiModule,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user