@thefehr/foundry-playwright 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +287 -0
- package/dist/canvas.d.ts +47 -0
- package/dist/canvas.js +105 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +83 -0
- package/dist/cli/init.d.ts +5 -0
- package/dist/cli/init.js +129 -0
- package/dist/deprecations.d.ts +24 -0
- package/dist/deprecations.js +59 -0
- package/dist/docker.d.ts +37 -0
- package/dist/docker.js +140 -0
- package/dist/fixtures.d.ts +29 -0
- package/dist/fixtures.js +112 -0
- package/dist/helpers.d.ts +100 -0
- package/dist/helpers.js +414 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/setup/base.d.ts +97 -0
- package/dist/setup/base.js +53 -0
- package/dist/setup/index.d.ts +14 -0
- package/dist/setup/index.js +126 -0
- package/dist/setup/v13.d.ts +28 -0
- package/dist/setup/v13.js +308 -0
- package/dist/setup/v14.d.ts +31 -0
- package/dist/setup/v14.js +421 -0
- package/dist/state.d.ts +139 -0
- package/dist/state.js +321 -0
- package/dist/systems/base.d.ts +48 -0
- package/dist/systems/base.js +57 -0
- package/dist/systems/dnd5e.d.ts +27 -0
- package/dist/systems/dnd5e.js +30 -0
- package/dist/systems/index.d.ts +13 -0
- package/dist/systems/index.js +20 -0
- package/dist/systems/pf2e.d.ts +25 -0
- package/dist/systems/pf2e.js +62 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.js +11 -0
- package/dist/ui/base.d.ts +35 -0
- package/dist/ui/base.js +43 -0
- package/dist/ui/dnd5e.d.ts +8 -0
- package/dist/ui/dnd5e.js +10 -0
- package/dist/ui/index.d.ts +45 -0
- package/dist/ui/index.js +72 -0
- package/dist/ui/tidy5e.d.ts +11 -0
- package/dist/ui/tidy5e.js +30 -0
- package/package.json +67 -0
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Action for the 'init' CLI command.
|
|
5
|
+
* Bootstraps a new Foundry E2E test project.
|
|
6
|
+
*/
|
|
7
|
+
export async function initAction() {
|
|
8
|
+
console.log("đ Initializing foundry-playwright project...");
|
|
9
|
+
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
10
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
11
|
+
console.error("â package.json not found. Please run this in a Node.js project root.");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// 1. Update package.json scripts
|
|
15
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
16
|
+
packageJson.scripts = packageJson.scripts || {};
|
|
17
|
+
const testCommand = "foundry-playwright test --docker";
|
|
18
|
+
if (!packageJson.scripts["test:e2e"]) {
|
|
19
|
+
packageJson.scripts["test:e2e"] = testCommand;
|
|
20
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
21
|
+
console.log("â
Added 'test:e2e' script to package.json");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
console.log("âšī¸ 'test:e2e' script already exists in package.json. Skipping.");
|
|
25
|
+
}
|
|
26
|
+
// 2. Create playwright.config.ts
|
|
27
|
+
const configPath = path.join(process.cwd(), "playwright.config.ts");
|
|
28
|
+
if (!fs.existsSync(configPath)) {
|
|
29
|
+
const configContent = `import { defineConfig, devices } from "@playwright/test";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Playwright configuration for Foundry VTT E2E testing.
|
|
33
|
+
* See https://playwright.dev/docs/test-configuration.
|
|
34
|
+
*/
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
testDir: "./e2e",
|
|
37
|
+
timeout: 120000, // 2 minutes for slow Foundry boots
|
|
38
|
+
expect: {
|
|
39
|
+
timeout: 10000,
|
|
40
|
+
},
|
|
41
|
+
/* Run tests in files in parallel */
|
|
42
|
+
fullyParallel: true,
|
|
43
|
+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
|
44
|
+
reporter: [["html", { open: "never" }]],
|
|
45
|
+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
|
46
|
+
use: {
|
|
47
|
+
/* Base URL to use in actions like \`await page.goto('/')\`. */
|
|
48
|
+
baseURL: process.env.FOUNDRY_URL || "http://localhost:30000",
|
|
49
|
+
|
|
50
|
+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
|
51
|
+
trace: "on-first-retry",
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/* Configure projects for major browsers */
|
|
55
|
+
projects: [
|
|
56
|
+
{
|
|
57
|
+
name: "chromium",
|
|
58
|
+
use: {
|
|
59
|
+
...devices["Desktop Chrome"],
|
|
60
|
+
viewport: { width: 1440, height: 900 },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
`;
|
|
66
|
+
fs.writeFileSync(configPath, configContent);
|
|
67
|
+
console.log("â
Created playwright.config.ts");
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.log("âšī¸ playwright.config.ts already exists. Skipping.");
|
|
71
|
+
}
|
|
72
|
+
// 3. Create e2e directory and a sample test
|
|
73
|
+
const e2eDir = path.join(process.cwd(), "e2e");
|
|
74
|
+
if (!fs.existsSync(e2eDir)) {
|
|
75
|
+
fs.mkdirSync(e2eDir);
|
|
76
|
+
console.log("â
Created 'e2e' directory");
|
|
77
|
+
}
|
|
78
|
+
const sampleTestPath = path.join(e2eDir, "basic.spec.ts");
|
|
79
|
+
if (!fs.existsSync(sampleTestPath)) {
|
|
80
|
+
const sampleTestContent = `import { test, expect, useFoundry } from "@thefehr/foundry-playwright";
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* This helper ensures that the Foundry VTT instance is fully set up
|
|
84
|
+
* with the desired world, system, and modules before any tests run.
|
|
85
|
+
*/
|
|
86
|
+
useFoundry(test, {
|
|
87
|
+
worldId: "test-world",
|
|
88
|
+
systemId: "dnd5e",
|
|
89
|
+
// moduleId: "my-module", // Uncomment and replace with your module ID
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("Foundry VTT is loaded and reachable", async ({ page }) => {
|
|
93
|
+
await page.goto("/");
|
|
94
|
+
|
|
95
|
+
// Wait for the game to be ready (handled by foundrySetup, but good to check)
|
|
96
|
+
await expect(page).toHaveTitle(/Foundry VTT/);
|
|
97
|
+
|
|
98
|
+
const gamePaused = await page.evaluate(() => game.paused);
|
|
99
|
+
console.log("Is the game paused?", gamePaused);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("Can interact with Foundry State", async ({ foundry }) => {
|
|
103
|
+
// The 'foundry' fixture provides direct access to state manipulation
|
|
104
|
+
const actors = await foundry.state.getDocuments("Actor");
|
|
105
|
+
console.log(\`Found \${actors.length} actors in the world.\`);
|
|
106
|
+
});
|
|
107
|
+
`;
|
|
108
|
+
fs.writeFileSync(sampleTestPath, sampleTestContent);
|
|
109
|
+
console.log("â
Created e2e/basic.spec.ts (sample test)");
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.log("âšī¸ e2e/basic.spec.ts already exists. Skipping.");
|
|
113
|
+
}
|
|
114
|
+
// 4. Create .env.template
|
|
115
|
+
const envTemplatePath = path.join(process.cwd(), ".env.template");
|
|
116
|
+
if (!fs.existsSync(envTemplatePath)) {
|
|
117
|
+
const envContent = `FOUNDRY_ADMIN_KEY=password
|
|
118
|
+
FOUNDRY_VERSION=13.351.0
|
|
119
|
+
FOUNDRY_SYSTEM_ID=dnd5e
|
|
120
|
+
`;
|
|
121
|
+
fs.writeFileSync(envTemplatePath, envContent);
|
|
122
|
+
console.log("â
Created .env.template");
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log("âšī¸ .env.template already exists. Skipping.");
|
|
126
|
+
}
|
|
127
|
+
console.log("\nđ Setup complete! You can now run your tests with:");
|
|
128
|
+
console.log(" npm run test:e2e");
|
|
129
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped tracker for deprecation and warning messages.
|
|
3
|
+
* Allows adapters to register patterns that should be ignored or explicitly failed.
|
|
4
|
+
*/
|
|
5
|
+
export declare class DeprecationTracker {
|
|
6
|
+
private ignoredPatterns;
|
|
7
|
+
private failurePatterns;
|
|
8
|
+
/**
|
|
9
|
+
* Registers a pattern to be ignored.
|
|
10
|
+
*/
|
|
11
|
+
registerIgnore(pattern: string | RegExp | (string | RegExp)[]): void;
|
|
12
|
+
/**
|
|
13
|
+
* Registers a pattern that should explicitly fail the test, even if it doesn't contain "deprecated".
|
|
14
|
+
*/
|
|
15
|
+
registerFailure(pattern: string | RegExp | (string | RegExp)[]): void;
|
|
16
|
+
/**
|
|
17
|
+
* Checks if a warning message should be ignored.
|
|
18
|
+
*/
|
|
19
|
+
shouldIgnore(text: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a warning message should cause a test failure.
|
|
22
|
+
*/
|
|
23
|
+
shouldFail(text: string): boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scoped tracker for deprecation and warning messages.
|
|
3
|
+
* Allows adapters to register patterns that should be ignored or explicitly failed.
|
|
4
|
+
*/
|
|
5
|
+
export class DeprecationTracker {
|
|
6
|
+
ignoredPatterns = [
|
|
7
|
+
"namespaced under foundry", // V14 internal namespacing we handle
|
|
8
|
+
];
|
|
9
|
+
failurePatterns = [];
|
|
10
|
+
/**
|
|
11
|
+
* Registers a pattern to be ignored.
|
|
12
|
+
*/
|
|
13
|
+
registerIgnore(pattern) {
|
|
14
|
+
if (Array.isArray(pattern)) {
|
|
15
|
+
this.ignoredPatterns.push(...pattern);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this.ignoredPatterns.push(pattern);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Registers a pattern that should explicitly fail the test, even if it doesn't contain "deprecated".
|
|
23
|
+
*/
|
|
24
|
+
registerFailure(pattern) {
|
|
25
|
+
if (Array.isArray(pattern)) {
|
|
26
|
+
this.failurePatterns.push(...pattern);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.failurePatterns.push(pattern);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Checks if a warning message should be ignored.
|
|
34
|
+
*/
|
|
35
|
+
shouldIgnore(text) {
|
|
36
|
+
const lowerText = text.toLowerCase();
|
|
37
|
+
return this.ignoredPatterns.some((p) => {
|
|
38
|
+
if (typeof p === "string")
|
|
39
|
+
return lowerText.includes(p.toLowerCase());
|
|
40
|
+
return p.test(text);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks if a warning message should cause a test failure.
|
|
45
|
+
*/
|
|
46
|
+
shouldFail(text) {
|
|
47
|
+
const lowerText = text.toLowerCase();
|
|
48
|
+
// Default failure for deprecations
|
|
49
|
+
if (lowerText.includes("deprecated") || lowerText.includes("deprecation")) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
// Check custom failure patterns
|
|
53
|
+
return this.failurePatterns.some((p) => {
|
|
54
|
+
if (typeof p === "string")
|
|
55
|
+
return lowerText.includes(p.toLowerCase());
|
|
56
|
+
return p.test(text);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/docker.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface DockerOrchestratorConfig {
|
|
2
|
+
version: string;
|
|
3
|
+
port?: number;
|
|
4
|
+
adminKey?: string;
|
|
5
|
+
username?: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
dataDir?: string;
|
|
8
|
+
cacheDir?: string;
|
|
9
|
+
containerName?: string;
|
|
10
|
+
envFile?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Programmatic orchestrator for Foundry VTT Docker containers.
|
|
14
|
+
* Uses direct docker commands instead of docker-compose for better control and zero-config for users.
|
|
15
|
+
*/
|
|
16
|
+
export declare class DockerFoundryOrchestrator {
|
|
17
|
+
private config;
|
|
18
|
+
constructor(config: DockerOrchestratorConfig);
|
|
19
|
+
/**
|
|
20
|
+
* Starts the Foundry VTT container.
|
|
21
|
+
*/
|
|
22
|
+
start(): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Generates the docker run command.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
getRunCommand(envPath: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Stops and removes the container.
|
|
30
|
+
*/
|
|
31
|
+
stopAndRemove(): void;
|
|
32
|
+
/**
|
|
33
|
+
* Copies a local path into the container.
|
|
34
|
+
*/
|
|
35
|
+
copyToContainer(localPath: string, containerPath: string): void;
|
|
36
|
+
private waitForReady;
|
|
37
|
+
}
|
package/dist/docker.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
/**
|
|
5
|
+
* Programmatic orchestrator for Foundry VTT Docker containers.
|
|
6
|
+
* Uses direct docker commands instead of docker-compose for better control and zero-config for users.
|
|
7
|
+
*/
|
|
8
|
+
export class DockerFoundryOrchestrator {
|
|
9
|
+
config;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.config = {
|
|
12
|
+
version: config.version,
|
|
13
|
+
port: config.port || 30000,
|
|
14
|
+
adminKey: config.adminKey || "password",
|
|
15
|
+
username: config.username || process.env.FOUNDRY_USERNAME || "",
|
|
16
|
+
password: config.password || process.env.FOUNDRY_PASSWORD || "",
|
|
17
|
+
dataDir: config.dataDir || path.join(process.cwd(), "foundry_data"),
|
|
18
|
+
cacheDir: config.cacheDir || path.join(process.cwd(), ".foundry_cache"),
|
|
19
|
+
containerName: config.containerName || `foundry-playwright-${config.version.replace(/\./g, "-")}`,
|
|
20
|
+
envFile: config.envFile || ".env",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Starts the Foundry VTT container.
|
|
25
|
+
*/
|
|
26
|
+
async start() {
|
|
27
|
+
console.log(`[DockerOrchestrator] Starting Foundry VTT v${this.config.version}...`);
|
|
28
|
+
// 1. Verify environment file
|
|
29
|
+
const envPath = path.resolve(this.config.envFile);
|
|
30
|
+
if (!fs.existsSync(envPath)) {
|
|
31
|
+
throw new Error(`[DockerOrchestrator] Environment file not found at ${envPath}. A valid .env file is required to avoid leaking credentials in logs.`);
|
|
32
|
+
}
|
|
33
|
+
const envContent = fs.readFileSync(envPath, "utf8");
|
|
34
|
+
const requiredVars = ["FOUNDRY_USERNAME", "FOUNDRY_PASSWORD", "FOUNDRY_ADMIN_KEY"];
|
|
35
|
+
for (const v of requiredVars) {
|
|
36
|
+
const regex = new RegExp(`^[ \\t]*${v}=`, "m");
|
|
37
|
+
if (!regex.test(envContent)) {
|
|
38
|
+
throw new Error(`[DockerOrchestrator] Environment file at ${envPath} is missing required variable: ${v}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// 2. Ensure directories exist
|
|
42
|
+
if (!fs.existsSync(this.config.dataDir))
|
|
43
|
+
fs.mkdirSync(this.config.dataDir, { recursive: true });
|
|
44
|
+
if (!fs.existsSync(this.config.cacheDir))
|
|
45
|
+
fs.mkdirSync(this.config.cacheDir, { recursive: true });
|
|
46
|
+
// 3. Stop/Remove existing container if it exists
|
|
47
|
+
this.stopAndRemove();
|
|
48
|
+
// 4. Pull image if missing
|
|
49
|
+
const image = `ghcr.io/felddy/foundryvtt:${this.config.version}`;
|
|
50
|
+
const imageExists = execSync(`docker images -q ${image}`, { encoding: "utf8" }).trim() !== "";
|
|
51
|
+
if (!imageExists) {
|
|
52
|
+
console.log(`[DockerOrchestrator] Image ${image} not found locally. Pulling...`);
|
|
53
|
+
execSync(`docker pull ${image}`, { stdio: "inherit" });
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(`[DockerOrchestrator] Image ${image} already exists locally.`);
|
|
57
|
+
// Optional: try to pull to update, but ignore failures
|
|
58
|
+
try {
|
|
59
|
+
console.log(`[DockerOrchestrator] Attempting to update image ${image}...`);
|
|
60
|
+
execSync(`docker pull ${image}`, { stdio: "ignore" });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
console.warn(`[DockerOrchestrator] Failed to update image ${image}, using local version.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 5. Run container
|
|
67
|
+
const dockerCmd = this.getRunCommand(envPath);
|
|
68
|
+
console.log(`[DockerOrchestrator] Executing: docker run -d --name ${this.config.containerName} ... (using --env-file for security)`);
|
|
69
|
+
execSync(dockerCmd, { stdio: "inherit" });
|
|
70
|
+
// 6. Wait for healthy
|
|
71
|
+
await this.waitForReady();
|
|
72
|
+
return `http://localhost:${this.config.port}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generates the docker run command.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
getRunCommand(envPath) {
|
|
79
|
+
const image = `ghcr.io/felddy/foundryvtt:${this.config.version}`;
|
|
80
|
+
return [
|
|
81
|
+
"docker run -d",
|
|
82
|
+
`--name ${this.config.containerName}`,
|
|
83
|
+
"--restart always",
|
|
84
|
+
`-p ${this.config.port}:30000`,
|
|
85
|
+
`--env-file "${path.resolve(envPath)}"`,
|
|
86
|
+
`-v "${path.resolve(this.config.dataDir)}:/data"`,
|
|
87
|
+
`-v "${path.resolve(this.config.cacheDir)}:/data/container_cache"`,
|
|
88
|
+
image,
|
|
89
|
+
].join(" ");
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Stops and removes the container.
|
|
93
|
+
*/
|
|
94
|
+
stopAndRemove() {
|
|
95
|
+
console.log(`[DockerOrchestrator] Cleaning up container ${this.config.containerName}...`);
|
|
96
|
+
try {
|
|
97
|
+
execSync(`docker stop ${this.config.containerName}`, { stdio: "ignore" });
|
|
98
|
+
execSync(`docker rm ${this.config.containerName}`, { stdio: "ignore" });
|
|
99
|
+
}
|
|
100
|
+
catch { }
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Copies a local path into the container.
|
|
104
|
+
*/
|
|
105
|
+
copyToContainer(localPath, containerPath) {
|
|
106
|
+
console.log(`[DockerOrchestrator] Copying ${localPath} to ${this.config.containerName}:${containerPath}`);
|
|
107
|
+
// Ensure destination directory exists via an ephemeral container or exec (if running)
|
|
108
|
+
execSync(`docker exec ${this.config.containerName} mkdir -p ${path.dirname(containerPath)}`, {
|
|
109
|
+
stdio: "inherit",
|
|
110
|
+
});
|
|
111
|
+
execSync(`docker cp ${localPath} ${this.config.containerName}:${containerPath}`, {
|
|
112
|
+
stdio: "inherit",
|
|
113
|
+
});
|
|
114
|
+
// Fix permissions
|
|
115
|
+
execSync(`docker exec ${this.config.containerName} chown -R 1000:1000 ${containerPath}`, {
|
|
116
|
+
stdio: "inherit",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async waitForReady() {
|
|
120
|
+
const url = `http://localhost:${this.config.port}`;
|
|
121
|
+
console.log(`[DockerOrchestrator] Waiting for Foundry to be ready at ${url}...`);
|
|
122
|
+
let ready = false;
|
|
123
|
+
const maxAttempts = 150;
|
|
124
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch(url);
|
|
127
|
+
if (response.ok) {
|
|
128
|
+
ready = true;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
134
|
+
}
|
|
135
|
+
if (!ready) {
|
|
136
|
+
throw new Error("Foundry VTT failed to start within the timeout period.");
|
|
137
|
+
}
|
|
138
|
+
console.log("[DockerOrchestrator] Foundry is ready!");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { FoundryState } from "./state.js";
|
|
2
|
+
import { FoundryUI } from "./ui/index.js";
|
|
3
|
+
import { FoundryCanvas } from "./canvas.js";
|
|
4
|
+
import { DeprecationTracker } from "./deprecations.js";
|
|
5
|
+
import { FoundryPage } from "./types/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Extended Playwright test fixtures for Foundry VTT.
|
|
8
|
+
*/
|
|
9
|
+
export interface FoundryFixtures {
|
|
10
|
+
/** Helper for Foundry VTT utilities. */
|
|
11
|
+
foundry: {
|
|
12
|
+
/** Direct state manipulation. */
|
|
13
|
+
state: FoundryState;
|
|
14
|
+
/** UI interaction and selectors. */
|
|
15
|
+
ui: FoundryUI;
|
|
16
|
+
/** WebGL Canvas interaction. */
|
|
17
|
+
canvas: FoundryCanvas;
|
|
18
|
+
};
|
|
19
|
+
/** Tracker for deprecation warnings. */
|
|
20
|
+
deprecationTracker: DeprecationTracker;
|
|
21
|
+
/** The Playwright Page object. */
|
|
22
|
+
page: FoundryPage;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Extended Playwright test fixture that monitors the browser console for critical errors and warnings,
|
|
26
|
+
* and provides Foundry-specific utilities.
|
|
27
|
+
*/
|
|
28
|
+
export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & FoundryFixtures, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
29
|
+
export { expect } from "@playwright/test";
|
package/dist/fixtures.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { test as base } from "@playwright/test";
|
|
2
|
+
import { FoundryState } from "./state.js";
|
|
3
|
+
import { FoundryUI } from "./ui/index.js";
|
|
4
|
+
import { FoundryCanvas } from "./canvas.js";
|
|
5
|
+
import { disableTour } from "./helpers.js";
|
|
6
|
+
import { DeprecationTracker } from "./deprecations.js";
|
|
7
|
+
import { initAllSystems } from "./systems/index.js";
|
|
8
|
+
/**
|
|
9
|
+
* Extended Playwright test fixture that monitors the browser console for critical errors and warnings,
|
|
10
|
+
* and provides Foundry-specific utilities.
|
|
11
|
+
*/
|
|
12
|
+
export const test = base.extend({
|
|
13
|
+
deprecationTracker: async (_, use) => {
|
|
14
|
+
await use(new DeprecationTracker());
|
|
15
|
+
},
|
|
16
|
+
foundry: async ({ page, deprecationTracker }, use) => {
|
|
17
|
+
const systemId = process.env.FOUNDRY_SYSTEM_ID || "dnd5e";
|
|
18
|
+
const uiAdapterId = process.env.FOUNDRY_UI_ADAPTER || "default";
|
|
19
|
+
await use({
|
|
20
|
+
state: new FoundryState(page, systemId, deprecationTracker),
|
|
21
|
+
ui: new FoundryUI(page, uiAdapterId),
|
|
22
|
+
canvas: new FoundryCanvas(page),
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
page: async ({ page, deprecationTracker }, use) => {
|
|
26
|
+
// 1. Initial page setup
|
|
27
|
+
await disableTour(page);
|
|
28
|
+
// Attach tracker to page for non-fixture access (e.g. in adapters)
|
|
29
|
+
const foundryPage = page;
|
|
30
|
+
foundryPage.deprecationTracker = deprecationTracker;
|
|
31
|
+
// Initialize all known systems to register their deprecation patterns
|
|
32
|
+
initAllSystems(foundryPage);
|
|
33
|
+
const deprecations = [];
|
|
34
|
+
page.on("console", (msg) => {
|
|
35
|
+
const text = msg.text();
|
|
36
|
+
const type = msg.type();
|
|
37
|
+
if (text.includes("hardware acceleration") ||
|
|
38
|
+
text.includes("Skipping game canvas") ||
|
|
39
|
+
text.includes("Buffered socket event") ||
|
|
40
|
+
text.includes("[vite]"))
|
|
41
|
+
return;
|
|
42
|
+
if (type === "error")
|
|
43
|
+
console.error(`Browser Error: ${text}`);
|
|
44
|
+
if (type === "warning") {
|
|
45
|
+
if (deprecationTracker.shouldIgnore(text))
|
|
46
|
+
return;
|
|
47
|
+
console.warn(`Browser Warning: ${text}`);
|
|
48
|
+
if (deprecationTracker.shouldFail(text)) {
|
|
49
|
+
deprecations.push(text);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (text.includes("Cannot read properties of null") ||
|
|
53
|
+
text.includes("Failed data migration")) {
|
|
54
|
+
throw new Error(`Critical Warning: ${text}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (type === "log")
|
|
58
|
+
console.log(`Browser Log: ${text}`);
|
|
59
|
+
});
|
|
60
|
+
// 2. Pre-Test Synchronization
|
|
61
|
+
const syncModule = async () => {
|
|
62
|
+
const url = page.url();
|
|
63
|
+
if (url.includes("/game") || url.includes("/players")) {
|
|
64
|
+
console.log("[fixture] Synchronizing with game state...");
|
|
65
|
+
await page
|
|
66
|
+
.waitForFunction(() => window.FP_VERIFY !== undefined, { timeout: 30000 })
|
|
67
|
+
.catch(() => null);
|
|
68
|
+
// Wait for constructor readiness on V14
|
|
69
|
+
const isV14 = await page.evaluate(() => window.foundry?.applications?.api?.ApplicationV2 !== undefined ||
|
|
70
|
+
document.querySelector('script[src*="foundry.mjs"]') !== null);
|
|
71
|
+
if (isV14) {
|
|
72
|
+
await page
|
|
73
|
+
.waitForFunction(() => {
|
|
74
|
+
return (window.FakeAppV2 !== undefined && window.FakeTour !== undefined);
|
|
75
|
+
}, {}, { timeout: 15000 })
|
|
76
|
+
.catch(() => null);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
await syncModule();
|
|
81
|
+
try {
|
|
82
|
+
await use(foundryPage);
|
|
83
|
+
// Report deprecations at the end of a successful test run
|
|
84
|
+
if (deprecations.length > 0) {
|
|
85
|
+
const uniqueDeprecations = Array.from(new Set(deprecations));
|
|
86
|
+
throw new Error(`Deprecation Warnings detected during test:\n${uniqueDeprecations.join("\n")}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const verifyData = await page
|
|
91
|
+
.evaluate(() => {
|
|
92
|
+
if (!window.FP_VERIFY)
|
|
93
|
+
return null;
|
|
94
|
+
// Return a shallow copy of log keys to avoid massive serialization
|
|
95
|
+
const logs = window.FP_VERIFY.logs;
|
|
96
|
+
const summary = {};
|
|
97
|
+
for (let key in logs) {
|
|
98
|
+
summary[key] = logs[key].length + " entries";
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
summary,
|
|
102
|
+
lastLogs: Object.fromEntries(Object.entries(logs).map(([k, v]) => [k, v.slice(-1)])),
|
|
103
|
+
};
|
|
104
|
+
})
|
|
105
|
+
.catch(() => null);
|
|
106
|
+
if (verifyData)
|
|
107
|
+
console.error(`[FP_VERIFY DUMP on Failure]:\n${JSON.stringify(verifyData, null, 2)}`);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
export { expect } from "@playwright/test";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Page, Locator } from "@playwright/test";
|
|
2
|
+
/**
|
|
3
|
+
* Loads the verification registry from verified-versions.json.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getVerificationRegistry(): any[];
|
|
6
|
+
/**
|
|
7
|
+
* Helper to automatically set up a Foundry VTT instance before all tests in a file.
|
|
8
|
+
* Reduces boilerplate in spec files.
|
|
9
|
+
* @param test The Playwright test object.
|
|
10
|
+
* @param config Foundry setup configuration.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useFoundry(test: any, config?: any): void;
|
|
13
|
+
/**
|
|
14
|
+
* Validates the current Foundry/System/Module stack against the registry.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateStack(page: Page, targetVersion?: string | number): Promise<"untested" | "incompatible" | "stable" | undefined>;
|
|
17
|
+
/**
|
|
18
|
+
* Aggressively removes Foundry VTT tours and overlays from the DOM and localStorage.
|
|
19
|
+
* @param page The Playwright Page object.
|
|
20
|
+
*/
|
|
21
|
+
export declare function disableTour(page: Page): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Navigates to a specific tab by name or ID.
|
|
24
|
+
* @param page The Playwright Page object.
|
|
25
|
+
* @param tabName The logical name or data-tab value of the tab.
|
|
26
|
+
*/
|
|
27
|
+
export declare function switchTab(page: Page, tabName: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Navigates to the Systems tab and opens the installation dialog.
|
|
30
|
+
* @param page The Playwright Page object.
|
|
31
|
+
*/
|
|
32
|
+
export declare function openSystemInstallDialog(page: Page): Promise<Locator>;
|
|
33
|
+
/**
|
|
34
|
+
* Navigates to the Modules tab and opens the installation dialog.
|
|
35
|
+
* @param page The Playwright Page object.
|
|
36
|
+
*/
|
|
37
|
+
export declare function openModuleInstallDialog(page: Page): Promise<Locator>;
|
|
38
|
+
/**
|
|
39
|
+
* Installs a system from a manifest URL.
|
|
40
|
+
* @param page The Playwright Page object.
|
|
41
|
+
* @param manifestUrl The URL to the system.json manifest.
|
|
42
|
+
*/
|
|
43
|
+
export declare function installSystemFromManifest(page: Page, manifestUrl: string): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Installs a module from a manifest URL.
|
|
46
|
+
* @param page The Playwright Page object.
|
|
47
|
+
* @param manifestUrl The URL to the module.json manifest.
|
|
48
|
+
*/
|
|
49
|
+
export declare function installModuleFromManifest(page: Page, manifestUrl: string): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Waits for the Foundry VTT game object to be fully initialized and ready.
|
|
52
|
+
* @param page The Playwright Page object.
|
|
53
|
+
*/
|
|
54
|
+
export declare function waitForReady(page: Page): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Executes a function after ensuring a log has been emitted via FP_VERIFY.
|
|
57
|
+
* @param page The Playwright Page object.
|
|
58
|
+
* @param key The log key to wait for.
|
|
59
|
+
* @param predicate A function to test the log data.
|
|
60
|
+
* @param extraData Optional extra data to pass to the predicate.
|
|
61
|
+
*/
|
|
62
|
+
export declare function verifyResult(page: Page, key: string, predicate: (data: any, extra?: any) => boolean, extraData?: any, options?: {
|
|
63
|
+
timeout?: number;
|
|
64
|
+
}): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Waits for a specific actor flag to be set to a value.
|
|
67
|
+
*/
|
|
68
|
+
export declare function waitForActorFlag(page: Page, actorName: string, flag: string, value: any): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Waits for specific actor data to be updated.
|
|
71
|
+
*/
|
|
72
|
+
export declare function waitForActorData(page: Page, actorName: string, path: string, value: any): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Waits for a game setting to be set.
|
|
75
|
+
*/
|
|
76
|
+
export declare function waitForSetting(page: Page, module: string, key: string, value: any): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Clears the FP_VERIFY log registry.
|
|
79
|
+
*/
|
|
80
|
+
export declare function clearFPVerify(page: Page): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Handles the Foundry VTT reload dialog.
|
|
83
|
+
*/
|
|
84
|
+
export declare function handleReload(page: Page): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Fills a field in a visible dialog.
|
|
87
|
+
*/
|
|
88
|
+
export declare function fillDialogField(page: Page, label: string, value: string): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Performs the full module activation flow for a list of modules.
|
|
91
|
+
*/
|
|
92
|
+
export declare function handleModuleActivationFlow(page: Page, moduleIds: string[]): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Simulates a drop from a compendium onto a target.
|
|
95
|
+
*/
|
|
96
|
+
export declare function dropCompendiumItem(page: Page, targetSelector: string, pack: string, itemId: string): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Simulates a Foundry VTT drag-and-drop event.
|
|
99
|
+
*/
|
|
100
|
+
export declare function simulateFoundryDrop(page: Page, targetSelector: string, data: any): Promise<void>;
|