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
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Command-line interface for Quant Cloud Platform integration and management.
- **Log Streaming** - Live log tailing with follow mode
- **SSH Access** - Direct terminal access to cloud environments via AWS ECS Exec with interactive shells and one-shot commands
- **Backup Management** - Create, list, download, and delete environment backups
- **Visual Regression Testing** - Automated visual comparison between Quant projects and remote URLs with Playwright
- **Secure OAuth Authentication** - Browser-based login flow with PKCE security
- **Non-Interactive Mode** - All commands support context overrides via CLI flags for automation

Expand Down Expand Up @@ -116,6 +117,9 @@ qc env list # Still finds .quant.yml at project root
- `qc app select [appId]` - Switch to application (auto-prompts for environment)
- `qc app current` - Show current application context

### Projects
- `qc project list` - List Quant projects in current organization

### Environments
- `qc env list` - List environments in current application
- `qc env select [envId]` - Switch to environment with searchable selection
Expand Down Expand Up @@ -187,6 +191,70 @@ qc backup create --org=my-org --app=my-app --env=production --type=database --de
qc backup download backup-123 --org=my-org --app=my-app --env=production --type=filesystem
```

### Visual Regression Testing (VRT)

Run automated visual regression testing to compare Quant projects against remote URLs using Playwright and Chromium.

- `qc vrt` - Run VRT for all configured projects
- `qc vrt --project=project1,project2` - Run VRT for specific projects
- `qc vrt --threshold=0.05` - Set pixel difference threshold (0-1, default: 0.01)
- `qc vrt --max-pages=20` - Set maximum pages to crawl per project
- `qc vrt --max-depth=5` - Set maximum crawl depth
- `qc vrt --csv=report.csv` - Generate CSV report
- `qc vrt --output-dir=./screenshots` - Set screenshot output directory
- `qc vrt --quant-auth=user:pass` - Basic auth for Quant URLs
- `qc vrt --remote-auth=user:pass` - Basic auth for remote URLs

**Configuration:** Create `~/.quant/vrt-config.json` with project mappings:

```json
{
"projects": {
"simple-project": "https://example.com",
"project-with-auth": {
"url": "https://example2.com",
"remoteAuth": "user:pass",
"quantAuth": "user:pass"
}
},
"threshold": 0.01,
"maxPages": 10,
"maxDepth": 3,
"quantAuth": "default-user:default-pass",
"remoteAuth": "default-user:default-pass"
}
```

**Note:**
- Projects can be defined as simple URLs (strings) or objects with per-project auth
- Global `quantAuth`/`remoteAuth` apply to all projects unless overridden at project level
- CLI flags (`--quant-auth`, `--remote-auth`) override all config values

**Examples:**

```bash
# Run VRT for all configured projects
qc vrt

# Run for specific projects
qc vrt --project=my-project

# Run with custom threshold and generate CSV
qc vrt --threshold=0.02 --csv=vrt-report.csv

# Run with authentication
qc vrt --quant-auth=user:pass --remote-auth=user:pass

# Run with custom limits
qc vrt --max-pages=50 --max-depth=5 --output-dir=./my-screenshots
```

**Output:**
- Console summary with pass/fail status per page
- Screenshot diffs saved to `./vrt-results/{project}/{date}/`
- Optional CSV report with detailed results
- Exit code 1 if any tests fail

### Non-Interactive Mode

All commands support context override flags for automation and CI/CD:
Expand Down
96 changes: 93 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@
"inquirer-autocomplete-prompt": "^3.0.1",
"js-yaml": "^4.1.0",
"node-fetch": "^3.3.2",
"open": "^9.1.0"
"open": "^9.1.0",
"pixelmatch": "^6.0.0",
"playwright": "^1.40.0",
"pngjs": "^7.0.0"
},
"devDependencies": {
"@types/express": "^4.17.21",
Expand All @@ -68,6 +71,8 @@
"@types/jest": "^30.0.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.8.0",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.5",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"eslint": "^8.50.0",
Expand Down
90 changes: 90 additions & 0 deletions src/commands/project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { createSpinner } from '../utils/spinner.js';
import { ApiClient } from '../utils/api.js';
import { Logger } from '../utils/logger.js';

const logger = new Logger('Project');

export function projectCommand(program: Command) {
const project = program.command('project').description('Manage Quant projects');

project.command('list')
.description('List projects in the active organization')
.option('--org <org>', 'override organization')
.option('--platform <platform>', 'platform to use (override active platform)')
.action(async (options) => {
await handleProjectList(options);
});

return project;
}

interface ProjectOptions {
org?: string;
platform?: string;
}

async function handleProjectList(options: ProjectOptions) {
const spinner = createSpinner('Loading projects...');

try {
const client = await ApiClient.create({
org: options.org,
platform: options.platform
});
const projects = await client.getProjects({ organizationId: options.org });

spinner.succeed(`Found ${projects.length} project${projects.length !== 1 ? 's' : ''}`);

if (projects.length === 0) {
logger.info('No projects found in this organization.');
logger.info(`${chalk.gray('Create your first project in the dashboard')}`);
return;
}

logger.info('\nProjects:');
projects.forEach((project: any, index) => {
const name = project.name || project.machine_name;
const machineName = project.machine_name;
const domain = project.domain || project.url || project.aws_cloudfront_domain_name;

// Compact format: machine_name (name) - domain
let displayLine = ` ${chalk.cyan(machineName)}`;

if (name !== machineName) {
displayLine += ` ${chalk.gray(`(${name})`)}`;
}

if (domain) {
displayLine += ` - ${chalk.blue(domain)}`;
}

if (project.region) {
displayLine += ` ${chalk.gray(`[${project.region}]`)}`;
}

logger.info(displayLine);
});

// Show context
const orgInfo = options.org ? ` (org: ${options.org})` : ' (active organization)';
logger.info(`\n${chalk.gray('Showing projects for')}${orgInfo}`);

} catch (error: any) {
spinner.fail('Failed to load projects');

if (error.message?.includes('Not authenticated')) {
logger.info('Not authenticated. Run `quant-cloud login` to authenticate.');
} else if (error.message?.includes('404') || error.message?.includes('not found')) {
logger.error('Organization not found or no access to projects.');
logger.info(`${chalk.gray('Use')} ${chalk.cyan('quant-cloud org list')} ${chalk.gray('to see available organizations')}`);
} else if (error.message?.includes('403') || error.message?.includes('forbidden')) {
logger.error('Access denied. You may not have permission to view projects in this organization.');
} else {
logger.error('Error:', error.message);
logger.debug('Full error:', error);
}
}
}

Loading
Loading