Story 4.1: Task 1 Complete - PlayerPrivacyManager Core Logic
- Created src/contracts/privacy-settings.js with: - PrivacySettings typedef - PRIVACY_SETTINGS_DEFAULT (both flags false) - PRIVACY_SETTING_KEYS and FEATURE_NAME_MAP constants - createPrivacySettings() factory - isValidPrivacySettings() validator - validateSettingKey(), validateSettingValue(), validateFeatureName() helpers - Created src/core/PlayerPrivacyManager.js with: - Constructor with FoundryAdapter DI validation - getSettings(userId) - retrieves settings from user flags - setSetting(userId, key, value) - async, validates, persists via user.setFlag - isOptedIn(userId, feature) - convenience method for feature checks - getAllSettings() - aggregates all users' settings (GM view) - onChange(callback) - subscription pattern for change events - teardown() - cleanup - Created tests/unit/contracts/privacy-settings.test.js - 44 tests - Created tests/unit/core/PlayerPrivacyManager.test.js - 35 tests - All tests passing, lint clean - Updated sprint-status.yaml: 4-1 from ready-for-dev to in-progress - Updated story file: Task 1 subtasks 1.1-1.8 marked complete Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
const WebSocket = require('ws');
|
||||
const ws = new WebSocket('ws://localhost:9222/devtools/page/992C42C102A9604DCB6F7EE5CE6A5048');
|
||||
|
||||
console.log('\n========================================');
|
||||
console.log('Epic 3 Live Tests - Scene Presets');
|
||||
console.log('========================================\n');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Module is active',
|
||||
expr: 'game.modules.get("video-view-manager").active'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'User is GM',
|
||||
expr: 'game.user.isGM'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Current scene exists',
|
||||
expr: '!!game.scenes.current'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Can read scene flags',
|
||||
expr: '(typeof game.scenes.current.getFlag === "function")'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'Can write scene flags',
|
||||
expr: '(typeof game.scenes.current.setFlag === "function")'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'ui.notifications exists',
|
||||
expr: '!!ui.notifications'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: 'Hooks exists',
|
||||
expr: '!!Hooks'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'game.webrtc exists',
|
||||
expr: 'game.webrtc !== undefined'
|
||||
}
|
||||
];
|
||||
|
||||
let currentTest = 0;
|
||||
|
||||
ws.on('open', () => {
|
||||
console.log('✓ Connected to Foundry page\n');
|
||||
runNextTest();
|
||||
});
|
||||
|
||||
function runNextTest() {
|
||||
if (currentTest >= tests.length) {
|
||||
console.log('\n========================================');
|
||||
console.log('✓ All environment tests passed!');
|
||||
console.log('========================================\n');
|
||||
|
||||
console.log('Summary:');
|
||||
console.log('- FoundryVTT is running with module active');
|
||||
console.log('- User is GM (required for most Epic 3 features)');
|
||||
console.log('- Scene flag API is available');
|
||||
console.log('- Notification system is available');
|
||||
console.log('- Hooks system is available');
|
||||
console.log('\n✓ Environment is ready for Epic 3 testing!\n');
|
||||
|
||||
ws.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const test = tests[currentTest];
|
||||
ws.send(JSON.stringify({
|
||||
id: test.id,
|
||||
method: 'Runtime.evaluate',
|
||||
params: { expression: test.expr }
|
||||
}));
|
||||
}
|
||||
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const response = JSON.parse(data);
|
||||
const test = tests.find(t => t.id === response.id);
|
||||
|
||||
if (test) {
|
||||
const value = response.result?.result?.value;
|
||||
const passed = value === true || value === 'function' || value === 'object';
|
||||
const status = passed ? '✓' : '✗';
|
||||
console.log(`${status} ${test.name}`);
|
||||
currentTest++;
|
||||
runNextTest();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing response:', e.message);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (e) => {
|
||||
console.error('✗ WebSocket error:', e.message);
|
||||
ws.close();
|
||||
});
|
||||
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Epic 3 Scene Presets - Integration Tests
|
||||
* Tests the deployed video-view-manager module on localhost:31000
|
||||
* Run with: npx playwright test _bmad-output/tests/integration/epic-3-scene-presets.spec.js
|
||||
*
|
||||
* Note: Chrome DevTools must be running on port 9222 with FoundryVTT
|
||||
* Browser WSEndpoint: ws://localhost:9222/devtools/browser/1aeaf428-412f-4e20-9f2d-c13533d031ae
|
||||
*/
|
||||
|
||||
import { chromium, expect, test } from '@playwright/test';
|
||||
|
||||
// Configuration
|
||||
const CHROME_DEVTOOLS_URL = 'ws://localhost:9222/devtools/browser/1aeaf428-412f-4e20-9f2d-c13533d031ae';
|
||||
const FOUNDRY_URL = 'https://localhost:31000/game';
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
async function waitForModule(page) {
|
||||
await page.waitForFunction(() => {
|
||||
const module = game?.modules?.get('video-view-manager');
|
||||
return module?.active === true;
|
||||
}, { timeout: 30000 });
|
||||
}
|
||||
|
||||
async function waitForDirectorsBoard(page) {
|
||||
await page.waitForFunction(() => {
|
||||
const board = game?.modules?.get('video-view-manager')?._directorsBoard;
|
||||
return board?.rendered === true;
|
||||
}, { timeout: 10000 });
|
||||
}
|
||||
|
||||
async function clickByDataAction(page, action) {
|
||||
await page.evaluate((action) => {
|
||||
const element = document.querySelector(`[data-action="${action}"]`);
|
||||
if (element) element.click();
|
||||
}, action);
|
||||
}
|
||||
|
||||
async function getPresetCount(page) {
|
||||
return await page.evaluate(() => {
|
||||
const scene = game.scenes.current;
|
||||
const presets = scene?.getFlag('video-view-manager', 'presets') || {};
|
||||
return Object.keys(presets.presets || {}).length;
|
||||
});
|
||||
}
|
||||
|
||||
async function verifyNotification(page, text) {
|
||||
await page.waitForFunction((text) => {
|
||||
const notifications = ui.notifications?.notifications || [];
|
||||
return notifications.some(n => n.message?.includes(text));
|
||||
}, { text, timeout: 5000 });
|
||||
}
|
||||
|
||||
async function openDirectorsBoard(page) {
|
||||
await page.keyboard.down('Control');
|
||||
await page.keyboard.press('Shift+V');
|
||||
await page.keyboard.up('Control');
|
||||
await waitForDirectorsBoard(page);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Test Suite: ScenePresetManager Core Functionality
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Epic 3 - ScenePresetManager Core', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
page = pages.find(p => p.url().includes('localhost:31000/game')) || pages[0];
|
||||
|
||||
if (!page.url().includes('localhost:31000')) {
|
||||
await page.goto(FOUNDRY_URL);
|
||||
}
|
||||
|
||||
await waitForModule(page);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('Module is active and ScenePresetManager exists', async () => {
|
||||
const isActive = await page.evaluate(() => {
|
||||
const module = game.modules.get('video-view-manager');
|
||||
return module?.active && module?.scenePresetManager;
|
||||
});
|
||||
expect(isActive).toBe(true);
|
||||
});
|
||||
|
||||
test('Save and load preset via DirectorsBoard', async () => {
|
||||
await openDirectorsBoard(page);
|
||||
|
||||
const initialCount = await getPresetCount(page);
|
||||
|
||||
await clickByDataAction(page, 'save-preset');
|
||||
|
||||
await page.waitForSelector('.dialog [name="presetName"]', { timeout: 5000 });
|
||||
await page.fill('.dialog [name="presetName"]', 'Epic 3 Test Preset');
|
||||
await page.click('.dialog button:has-text("Save")');
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const newCount = await getPresetCount(page);
|
||||
expect(newCount).toBe(initialCount + 1);
|
||||
|
||||
await clickByDataAction(page, 'load-preset');
|
||||
await page.waitForSelector('.dialog', { timeout: 5000 });
|
||||
await page.click('.dialog [data-preset-name="Epic 3 Test Preset"]');
|
||||
await page.click('.dialog button:has-text("Load")');
|
||||
|
||||
await verifyNotification(page, 'applied preset: Epic 3 Test Preset');
|
||||
});
|
||||
|
||||
test('Duplicate preset name shows confirmation dialog', async () => {
|
||||
await openDirectorsBoard(page);
|
||||
|
||||
await clickByDataAction(page, 'save-preset');
|
||||
await page.waitForSelector('.dialog [name="presetName"]', { timeout: 5000 });
|
||||
await page.fill('.dialog [name="presetName"]', 'Epic 3 Test Preset');
|
||||
|
||||
await page.waitForSelector('.dialog:has-text("already exists")', { timeout: 3000 });
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Test Suite: ConfirmationBar & Auto-Apply
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Epic 3 - ConfirmationBar & Auto-Apply', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
page = pages.find(p => p.url().includes('localhost:31000/game')) || pages[0];
|
||||
|
||||
if (!page.url().includes('localhost:31000')) {
|
||||
await page.goto(FOUNDRY_URL);
|
||||
}
|
||||
|
||||
await waitForModule(page);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('ConfirmationBar appears after preset load', async () => {
|
||||
await openDirectorsBoard(page);
|
||||
|
||||
await clickByDataAction(page, 'load-preset');
|
||||
await page.waitForSelector('.dialog', { timeout: 5000 });
|
||||
await page.click('.dialog [data-preset-name="Epic 3 Test Preset"]');
|
||||
await page.click('.dialog button:has-text("Load")');
|
||||
|
||||
await page.waitForSelector('.sp-confirmation-bar', { timeout: 5000 });
|
||||
const barText = await page.$eval('.sp-confirmation-bar', el => el.textContent);
|
||||
expect(barText).toContain('Preset applied');
|
||||
});
|
||||
|
||||
test('ConfirmationBar undo reverts matrix', async () => {
|
||||
const initialMatrix = await page.evaluate(() => {
|
||||
const controller = game.modules.get('video-view-manager')?._scryingPoolController;
|
||||
return JSON.stringify(controller?.visibilityMatrix);
|
||||
});
|
||||
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'load-preset');
|
||||
await page.waitForSelector('.dialog', { timeout: 5000 });
|
||||
await page.click('.dialog [data-preset-name="Epic 3 Test Preset"]');
|
||||
await page.click('.dialog button:has-text("Load")');
|
||||
|
||||
await page.waitForSelector('.sp-confirmation-bar', { timeout: 5000 });
|
||||
|
||||
await page.click('.sp-confirmation-bar [data-action="undo"]');
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
const revertedMatrix = await page.evaluate(() => {
|
||||
const controller = game.modules.get('video-view-manager')?._scryingPoolController;
|
||||
return JSON.stringify(controller?.visibilityMatrix);
|
||||
});
|
||||
|
||||
expect(revertedMatrix).toBe(initialMatrix);
|
||||
});
|
||||
|
||||
test('ConfirmationBar auto-dismisses after 8 seconds', async () => {
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'load-preset');
|
||||
await page.waitForSelector('.dialog', { timeout: 5000 });
|
||||
await page.click('.dialog [data-preset-name="Epic 3 Test Preset"]');
|
||||
await page.click('.dialog button:has-text("Load")');
|
||||
|
||||
await page.waitForSelector('.sp-confirmation-bar', { timeout: 5000 });
|
||||
|
||||
// Note: This test may be flaky depending on timing
|
||||
// The bar should auto-dismiss after 8 seconds
|
||||
await page.waitForTimeout(8500);
|
||||
const isHidden = await page.evaluate(() => {
|
||||
const bar = document.querySelector('.sp-confirmation-bar');
|
||||
return !bar || bar.offsetParent === null;
|
||||
});
|
||||
expect(isHidden).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Test Suite: Import/Export
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Epic 3 - Preset Import/Export', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
page = pages.find(p => p.url().includes('localhost:31000/game')) || pages[0];
|
||||
|
||||
if (!page.url().includes('localhost:31000')) {
|
||||
await page.goto(FOUNDRY_URL);
|
||||
}
|
||||
|
||||
await waitForModule(page);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('Export presets downloads JSON file', async () => {
|
||||
await openDirectorsBoard(page);
|
||||
|
||||
await clickByDataAction(page, 'export-presets');
|
||||
|
||||
const download = await page.waitForEvent('download');
|
||||
const path = await download.path();
|
||||
const { readFileSync } = await import('fs');
|
||||
const contents = readFileSync(path, 'utf8');
|
||||
const data = JSON.parse(contents);
|
||||
|
||||
expect(data._version).toBe(1);
|
||||
expect(data.presets).toBeDefined();
|
||||
expect(Object.keys(data.presets).length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('Import with merge adds new presets', async () => {
|
||||
// First export
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'export-presets');
|
||||
const download = await page.waitForEvent('download');
|
||||
const exportPath = await download.path();
|
||||
|
||||
// Delete all presets
|
||||
await page.evaluate(() => {
|
||||
const scene = game.scenes.current;
|
||||
scene.unsetFlag('video-view-manager', 'presets');
|
||||
});
|
||||
|
||||
// Import with merge
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'import-presets');
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles(exportPath);
|
||||
|
||||
await page.click('text="Merge"');
|
||||
await page.click('button:has-text("Import")');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
const finalCount = await getPresetCount(page);
|
||||
expect(finalCount).toBeGreaterThan(0);
|
||||
|
||||
await verifyNotification(page, 'Imported');
|
||||
});
|
||||
|
||||
test('Import with invalid JSON shows error', async () => {
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'import-presets');
|
||||
|
||||
const { writeFileSync } = await import('fs');
|
||||
const invalidJsonPath = '/tmp/invalid-presets.json';
|
||||
writeFileSync(invalidJsonPath, '{invalid json}');
|
||||
|
||||
const fileInput = page.locator('input[type="file"]');
|
||||
await fileInput.setInputFiles(invalidJsonPath);
|
||||
|
||||
await verifyNotification(page, 'Invalid JSON');
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Test Suite: Edge Cases
|
||||
// ============================================================================
|
||||
|
||||
test.describe('Epic 3 - Edge Cases', () => {
|
||||
let browser;
|
||||
let page;
|
||||
|
||||
test.beforeAll(async () => {
|
||||
browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
page = pages.find(p => p.url().includes('localhost:31000/game')) || pages[0];
|
||||
|
||||
if (!page.url().includes('localhost:31000')) {
|
||||
await page.goto(FOUNDRY_URL);
|
||||
}
|
||||
|
||||
await waitForModule(page);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('Load preset with no presets shows empty message', async () => {
|
||||
await page.evaluate(() => {
|
||||
const scene = game.scenes.current;
|
||||
scene.unsetFlag('video-view-manager', 'presets');
|
||||
});
|
||||
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'load-preset');
|
||||
|
||||
await page.waitForSelector('.dialog:has-text("No presets saved yet")', { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('Auto-apply respects per-scene disable', async () => {
|
||||
const hasSetting = await page.evaluate(() => {
|
||||
const scene = game.scenes.current;
|
||||
const autoApply = scene.getFlag('video-view-manager', 'autoApply') || {};
|
||||
return autoApply.enabled !== undefined;
|
||||
});
|
||||
|
||||
expect(hasSetting).toBe(true);
|
||||
});
|
||||
|
||||
test('Socket events fire correctly', async () => {
|
||||
await page.evaluate(() => {
|
||||
window.testEvents = [];
|
||||
Hooks.off('scrying-pool.preset.apply');
|
||||
Hooks.off('scrying-pool.preset.applied');
|
||||
});
|
||||
|
||||
await page.evaluate(() => {
|
||||
Hooks.on('scrying-pool.preset.apply', () => window.testEvents.push('apply'));
|
||||
Hooks.on('scrying-pool.preset.applied', () => window.testEvents.push('applied'));
|
||||
});
|
||||
|
||||
await openDirectorsBoard(page);
|
||||
await clickByDataAction(page, 'load-preset');
|
||||
await page.waitForSelector('.dialog', { timeout: 5000 });
|
||||
await page.click('.dialog [data-preset-name="Epic 3 Test Preset"]');
|
||||
await page.click('.dialog button:has-text("Load")');
|
||||
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
const events = await page.evaluate(() => window.testEvents);
|
||||
expect(events).toContain('apply');
|
||||
expect(events).toContain('applied');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Simple Epic 3 connectivity test
|
||||
* Tests basic module loading and ScenePresetManager availability
|
||||
*/
|
||||
|
||||
import { chromium, expect, test } from '@playwright/test';
|
||||
|
||||
const CHROME_DEVTOOLS_URL = 'ws://localhost:9222/devtools/browser/1aeaf428-412f-4e20-9f2d-c13533d031ae';
|
||||
const FOUNDRY_URL = 'https://localhost:31000/game';
|
||||
|
||||
test('Connect to Chrome DevTools and verify Foundry page', async () => {
|
||||
const browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
console.log('Available pages:', pages.length);
|
||||
|
||||
const foundryPage = pages.find(p => p.url().includes('localhost:31000/game'));
|
||||
expect(foundryPage).toBeDefined();
|
||||
|
||||
await foundryPage.goto(FOUNDRY_URL);
|
||||
|
||||
// Wait for Foundry to load
|
||||
await foundryPage.waitForFunction(() => window.game?.ready, { timeout: 30000 });
|
||||
|
||||
// Verify module is active
|
||||
const isActive = await foundryPage.evaluate(() => {
|
||||
const module = game.modules.get('video-view-manager');
|
||||
return module?.active;
|
||||
});
|
||||
|
||||
expect(isActive).toBe(true);
|
||||
console.log('✓ Module is active');
|
||||
|
||||
// Verify ScenePresetManager exists
|
||||
const hasPresetManager = await foundryPage.evaluate(() => {
|
||||
const module = game.modules.get('video-view-manager');
|
||||
return module?.scenePresetManager !== undefined;
|
||||
});
|
||||
|
||||
expect(hasPresetManager).toBe(true);
|
||||
console.log('✓ ScenePresetManager exists');
|
||||
|
||||
// Verify DirectorsBoard exists
|
||||
const hasDirectorsBoard = await foundryPage.evaluate(() => {
|
||||
const module = game.modules.get('video-view-manager');
|
||||
return module?._directorsBoard !== undefined;
|
||||
});
|
||||
|
||||
expect(hasDirectorsBoard).toBe(true);
|
||||
console.log('✓ DirectorsBoard exists');
|
||||
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('Verify ConfirmationBar exists', async () => {
|
||||
const browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
const foundryPage = pages.find(p => p.url().includes('localhost:31000/game')) || pages[0];
|
||||
|
||||
await foundryPage.goto(FOUNDRY_URL);
|
||||
await foundryPage.waitForFunction(() => window.game?.ready, { timeout: 30000 });
|
||||
|
||||
const hasConfirmationBar = await foundryPage.evaluate(() => {
|
||||
const module = game.modules.get('video-view-manager');
|
||||
return module?._confirmationBar !== undefined;
|
||||
});
|
||||
|
||||
expect(hasConfirmationBar).toBe(true);
|
||||
console.log('✓ ConfirmationBar exists');
|
||||
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
test('Verify PresetImportExportManager exists', async () => {
|
||||
const browser = await chromium.connect({
|
||||
wsEndpoint: CHROME_DEVTOOLS_URL
|
||||
});
|
||||
|
||||
const pages = await browser.pages();
|
||||
const foundryPage = pages.find(p => p.url().includes('localhost:31000/game')) || pages[0];
|
||||
|
||||
await foundryPage.goto(FOUNDRY_URL);
|
||||
await foundryPage.waitForFunction(() => window.game?.ready, { timeout: 30000 });
|
||||
|
||||
const hasImportExportManager = await foundryPage.evaluate(() => {
|
||||
const module = game.modules.get('video-view-manager');
|
||||
return module?.presetImportExportManager !== undefined;
|
||||
});
|
||||
|
||||
expect(hasImportExportManager).toBe(true);
|
||||
console.log('✓ PresetImportExportManager exists');
|
||||
|
||||
await browser.close();
|
||||
});
|
||||
Reference in New Issue
Block a user