Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 28 additions & 52 deletions clients/ember/bin/vizzly-testem-launcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,17 @@ let isShuttingDown = false;

/**
* Clean up resources and exit
* @param {string} reason - Why cleanup was triggered
* @param {number} exitCode - Process exit code (default 0)
*/
async function cleanup() {
async function cleanup(reason = 'unknown', exitCode = 0) {
if (isShuttingDown) return;
isShuttingDown = true;

if (process.env.VIZZLY_LOG_LEVEL === 'debug') {
console.error(`[vizzly-testem-launcher] Cleanup triggered: ${reason}`);
}

try {
if (browserInstance) {
await closeBrowser(browserInstance);
Expand All @@ -82,7 +88,7 @@ async function cleanup() {
// Ignore cleanup errors
}

process.exit(0);
process.exit(exitCode);
}

/**
Expand Down Expand Up @@ -112,58 +118,23 @@ async function main() {
playwrightOptions,
onPageCreated: page => {
setPage(page);
page.on('close', cleanup);
page.on('close', async () => await cleanup('page-close'));
},
onBrowserDisconnected: async () => await cleanup('browser-disconnected'),
});

// 4. Monitor for test completion
// 4. Listen for browser crashes
let { page } = browserInstance;

// Wait for a test framework to be available, then hook into its completion
await page.evaluate(() => {
return new Promise(resolve => {
let checkFramework = () => {
// Check for QUnit
if (typeof QUnit !== 'undefined') {
QUnit.done(() => {
console.log('[vizzly-testem] tests-complete');
});
resolve();
return;
}

// Check for Mocha
if (typeof Mocha !== 'undefined' || typeof mocha !== 'undefined') {
let Runner = (typeof Mocha !== 'undefined' ? Mocha : mocha).Runner;
let originalEmit = Runner.prototype.emit;
Runner.prototype.emit = function (...args) {
if (args[0] === 'end') {
console.log('[vizzly-testem] tests-complete');
}
return originalEmit.apply(this, args);
};
resolve();
return;
}

// Keep checking until a framework is found
requestAnimationFrame(checkFramework);
};
checkFramework();
});
});

// Listen for the completion signal
page.on('console', msg => {
if (msg.text() === '[vizzly-testem] tests-complete') {
cleanup();
}
page.on('crash', async () => {
console.error('[vizzly-testem-launcher] Page crashed!');
await cleanup('page-crash', 1);
});

// 5. Keep process alive until cleanup is called
await new Promise(() => {});
} catch (error) {
console.error('[vizzly-testem-launcher] Failed to start:', error.message);
console.error(error.stack);

if (screenshotServer) {
await stopScreenshotServer(screenshotServer).catch(() => {});
Expand All @@ -174,19 +145,24 @@ async function main() {
}

// Handle graceful shutdown signals from Testem
process.on('SIGTERM', cleanup);
process.on('SIGINT', cleanup);
process.on('SIGHUP', cleanup);
process.on('SIGTERM', async () => await cleanup('SIGTERM'));
process.on('SIGINT', async () => await cleanup('SIGINT'));
process.on('SIGHUP', async () => await cleanup('SIGHUP'));

// Handle unexpected errors
process.on('uncaughtException', error => {
process.on('uncaughtException', async error => {
console.error('[vizzly-testem-launcher] Uncaught exception:', error.message);
cleanup();
console.error(error.stack);
await cleanup('uncaughtException', 1);
});

process.on('unhandledRejection', reason => {
console.error('[vizzly-testem-launcher] Unhandled rejection:', reason);
cleanup();
process.on('unhandledRejection', async (reason, promise) => {
console.error('[vizzly-testem-launcher] Unhandled rejection at:', promise);
console.error('[vizzly-testem-launcher] Reason:', reason);
if (reason instanceof Error) {
console.error(reason.stack);
}
await cleanup('unhandledRejection', 1);
});

main();
8 changes: 8 additions & 0 deletions clients/ember/src/launcher/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function getDefaultChromiumArgs() {
* @param {boolean} [options.failOnDiff] - Whether tests should fail on visual diffs
* @param {Object} [options.playwrightOptions] - Playwright launch options (headless, slowMo, timeout, etc.)
* @param {Function} [options.onPageCreated] - Callback when page is created (before navigation)
* @param {Function} [options.onBrowserDisconnected] - Callback when browser disconnects unexpectedly
* @returns {Promise<Object>} Browser instance with page reference
*/
export async function launchBrowser(browserType, testUrl, options = {}) {
Expand All @@ -71,6 +72,7 @@ export async function launchBrowser(browserType, testUrl, options = {}) {
failOnDiff,
playwrightOptions = {},
onPageCreated,
onBrowserDisconnected,
} = options;

let factory = browserFactories[browserType];
Expand Down Expand Up @@ -101,6 +103,12 @@ export async function launchBrowser(browserType, testUrl, options = {}) {
};

let browser = await factory.launch(launchOptions);

// Listen for unexpected browser disconnection (crash, killed, etc.)
if (onBrowserDisconnected) {
browser.on('disconnected', onBrowserDisconnected);
}

let context = await browser.newContext();
let page = await context.newPage();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import Alert from 'test-ember-app/components/alert';
import { vizzlySnapshot } from '@vizzly-testing/ember/test-support';
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';

module('Integration | Component | Alert', function (hooks) {
setupRenderingTest(hooks);
Expand Down Expand Up @@ -33,7 +33,7 @@ module('Integration | Component | Alert', function (hooks) {
assert.dom('[data-test-alert="warning"]').exists();
assert.dom('[data-test-alert="error"]').exists();

await vizzlySnapshot('alert-variants');
await vizzlyScreenshot('alert-variants');
});

test('it renders without title', async function (assert) {
Expand All @@ -48,6 +48,6 @@ module('Integration | Component | Alert', function (hooks) {
assert.dom('[data-test-alert="no-title"]').exists();
assert.dom('[data-test-alert="no-title"] .alert-title').doesNotExist();

await vizzlySnapshot('alert-no-title');
await vizzlyScreenshot('alert-no-title');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click } from '@ember/test-helpers';
import Button from 'test-ember-app/components/button';
import { vizzlySnapshot } from '@vizzly-testing/ember/test-support';
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';

module('Integration | Component | Button', function (hooks) {
setupRenderingTest(hooks);
Expand All @@ -22,7 +22,7 @@ module('Integration | Component | Button', function (hooks) {
assert.dom('[data-test-button="danger"]').hasText('Danger');
assert.dom('[data-test-button="ghost"]').hasText('Ghost');

await vizzlySnapshot('button-variants');
await vizzlyScreenshot('button-variants');
});

test('it renders all sizes', async function (assert) {
Expand All @@ -38,7 +38,7 @@ module('Integration | Component | Button', function (hooks) {
assert.dom('[data-test-button="medium"]').exists();
assert.dom('[data-test-button="large"]').exists();

await vizzlySnapshot('button-sizes');
await vizzlyScreenshot('button-sizes');
});

test('it renders disabled state', async function (assert) {
Expand All @@ -52,7 +52,7 @@ module('Integration | Component | Button', function (hooks) {
assert.dom('[data-test-button="enabled"]').isNotDisabled();
assert.dom('[data-test-button="disabled"]').isDisabled();

await vizzlySnapshot('button-disabled-state');
await vizzlyScreenshot('button-disabled-state');
});

test('it handles click events', async function (assert) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import Card from 'test-ember-app/components/card';
import Button from 'test-ember-app/components/button';
import { vizzlySnapshot } from '@vizzly-testing/ember/test-support';
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';

module('Integration | Component | Card', function (hooks) {
setupRenderingTest(hooks);
Expand All @@ -20,7 +20,7 @@ module('Integration | Component | Card', function (hooks) {
assert.dom('[data-test-card="basic"]').exists();
assert.dom('[data-test-card="basic"] .card-title').hasText('Basic Card');

await vizzlySnapshot('card-basic');
await vizzlyScreenshot('card-basic');
});

test('it renders elevated card', async function (assert) {
Expand All @@ -34,7 +34,7 @@ module('Integration | Component | Card', function (hooks) {

assert.dom('[data-test-card="elevated"]').hasClass('elevated');

await vizzlySnapshot('card-elevated');
await vizzlyScreenshot('card-elevated');
});

test('it renders card with actions and footer', async function (assert) {
Expand All @@ -60,7 +60,7 @@ module('Integration | Component | Card', function (hooks) {
assert.dom('[data-test-card="full"] .card-actions').exists();
assert.dom('[data-test-card="full"] .card-footer').exists();

await vizzlySnapshot('card-with-actions-footer');
await vizzlyScreenshot('card-with-actions-footer');
});

test('it renders card without title', async function (assert) {
Expand All @@ -74,6 +74,6 @@ module('Integration | Component | Card', function (hooks) {

assert.dom('[data-test-card="no-title"] .card-header').doesNotExist();

await vizzlySnapshot('card-no-title');
await vizzlyScreenshot('card-no-title');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import DataTable from 'test-ember-app/components/data-table';
import { vizzlySnapshot } from '@vizzly-testing/ember/test-support';
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';

module('Integration | Component | DataTable', function (hooks) {
setupRenderingTest(hooks);
Expand Down Expand Up @@ -31,7 +31,7 @@ module('Integration | Component | DataTable', function (hooks) {
assert.dom('.table-header').exists({ count: 4 });
assert.dom('.table-row').exists({ count: 3 });

await vizzlySnapshot('data-table-with-data');
await vizzlyScreenshot('data-table-with-data');
});

test('it renders empty state', async function (assert) {
Expand All @@ -50,7 +50,7 @@ module('Integration | Component | DataTable', function (hooks) {

assert.dom('.table-empty').hasText('No users found');

await vizzlySnapshot('data-table-empty');
await vizzlyScreenshot('data-table-empty');
});

test('it renders with many rows', async function (assert) {
Expand All @@ -72,7 +72,7 @@ module('Integration | Component | DataTable', function (hooks) {

assert.dom('.table-row').exists({ count: 10 });

await vizzlySnapshot('data-table-many-rows');
await vizzlyScreenshot('data-table-many-rows');
});

test('it applies column alignment', async function (assert) {
Expand All @@ -86,6 +86,6 @@ module('Integration | Component | DataTable', function (hooks) {
assert.dom('.table-header.center').exists({ count: 2 });
assert.dom('.table-cell.center').exists({ count: 6 }); // 2 columns * 3 rows

await vizzlySnapshot('data-table-alignment');
await vizzlyScreenshot('data-table-alignment');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn } from '@ember/test-helpers';
import FormField from 'test-ember-app/components/form-field';
import { vizzlySnapshot } from '@vizzly-testing/ember/test-support';
import { vizzlyScreenshot } from '@vizzly-testing/ember/test-support';

module('Integration | Component | FormField', function (hooks) {
setupRenderingTest(hooks);
Expand All @@ -23,7 +23,7 @@ module('Integration | Component | FormField', function (hooks) {
assert.dom('[data-test-form-field="username"] .form-label').hasText('Username');
assert.dom('[data-test-form-field="username"] .form-hint').hasText('Must be at least 3 characters');

await vizzlySnapshot('form-field-text');
await vizzlyScreenshot('form-field-text');
});

test('it renders with error state', async function (assert) {
Expand All @@ -44,7 +44,7 @@ module('Integration | Component | FormField', function (hooks) {
assert.dom('[data-test-form-field="email"] .form-error').hasText('Please enter a valid email address');
assert.dom('[data-test-form-field="email"] .required').exists();

await vizzlySnapshot('form-field-error');
await vizzlyScreenshot('form-field-error');
});

test('it renders textarea', async function (assert) {
Expand All @@ -62,7 +62,7 @@ module('Integration | Component | FormField', function (hooks) {

assert.dom('[data-test-form-field="message"] textarea').exists();

await vizzlySnapshot('form-field-textarea');
await vizzlyScreenshot('form-field-textarea');
});

test('it renders disabled state', async function (assert) {
Expand All @@ -79,7 +79,7 @@ module('Integration | Component | FormField', function (hooks) {

assert.dom('[data-test-form-field="readonly"] input').isDisabled();

await vizzlySnapshot('form-field-disabled');
await vizzlyScreenshot('form-field-disabled');
});

test('it renders various input types', async function (assert) {
Expand Down Expand Up @@ -120,6 +120,6 @@ module('Integration | Component | FormField', function (hooks) {
assert.dom('[data-test-form-field="email-field"] input').hasAttribute('type', 'email');
assert.dom('[data-test-form-field="number"] input').hasAttribute('type', 'number');

await vizzlySnapshot('form-field-types');
await vizzlyScreenshot('form-field-types');
});
});
Loading
Loading