feat(ios-testing): Enhance deeplink testing and error handling
- Improve test data generation and validation - Add detailed logging of generated test data - Implement robust validation of required fields - Use ts-node script for test data generation - Add fallback data generation with validation - Enhance deeplink testing UX - Add interactive prompts between tests - Display detailed test progress and next steps - Improve error handling and test skip logic - Add comprehensive logging throughout test execution - Improve DeepLinkErrorView - Add detailed error information display - Show debug information for parameters and queries - Enhance UI with better styling and layout - Add safe area spacing for iOS - Refactor deeplink handling - Standardize route definitions - Improve parameter validation - Add better error logging
This commit is contained in:
1120
package-lock.json
generated
1120
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,21 @@
|
|||||||
|
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync } = require('fs');
|
const { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, readdirSync, statSync, accessSync } = require('fs');
|
||||||
|
const readline = require('readline');
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
const { constants } = require('fs');
|
||||||
|
|
||||||
|
const question = (prompt) => new Promise((resolve) => rl.question(prompt, resolve));
|
||||||
|
|
||||||
|
// Make sure to close readline at the end
|
||||||
|
process.on('SIGINT', () => {
|
||||||
|
rl.close();
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
|
||||||
// Format date as YYYY-MM-DD-HHMMSS
|
// Format date as YYYY-MM-DD-HHMMSS
|
||||||
const getLogFileName = () => {
|
const getLogFileName = () => {
|
||||||
@@ -316,122 +330,84 @@ const verifyXcodeInstallation = (log) => {
|
|||||||
|
|
||||||
// Generate test data using generate_data.ts
|
// Generate test data using generate_data.ts
|
||||||
const generateTestData = async (log) => {
|
const generateTestData = async (log) => {
|
||||||
log('🔄 Generating test data...');
|
log('\n🔍 DEBUG: Starting test data generation...');
|
||||||
|
|
||||||
// Check if test-scripts directory exists
|
// Check directory structure
|
||||||
if (!existsSync('test-scripts')) {
|
log('📁 Current directory:', process.cwd());
|
||||||
log('⚠️ test-scripts directory not found');
|
log('📁 Directory contents:', require('fs').readdirSync('.'));
|
||||||
log('⚠️ Current directory: ' + process.cwd());
|
|
||||||
|
|
||||||
// List directories to help debug
|
|
||||||
const { readdirSync } = require('fs');
|
|
||||||
log('📂 Directories in current path:');
|
|
||||||
try {
|
|
||||||
const files = readdirSync('.');
|
|
||||||
files.forEach(file => {
|
|
||||||
const isDir = existsSync(file) && require('fs').statSync(file).isDirectory();
|
|
||||||
log(`${isDir ? '📁' : '📄'} ${file}`);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
log(`⚠️ Error listing directory: ${err.message}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('✅ Found test-scripts directory');
|
|
||||||
|
|
||||||
// Check if generate_data.ts exists
|
|
||||||
if (existsSync('test-scripts/generate_data.ts')) {
|
|
||||||
log('✅ Found generate_data.ts');
|
|
||||||
} else {
|
|
||||||
log('⚠️ generate_data.ts not found in test-scripts directory');
|
|
||||||
|
|
||||||
// List files in test-scripts to help debug
|
|
||||||
const { readdirSync } = require('fs');
|
|
||||||
log('📂 Files in test-scripts:');
|
|
||||||
try {
|
|
||||||
const files = readdirSync('test-scripts');
|
|
||||||
files.forEach(file => {
|
|
||||||
log(`📄 ${file}`);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
log(`⚠️ Error listing test-scripts: ${err.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create .generated directory if it doesn't exist
|
|
||||||
if (!existsSync('.generated')) {
|
if (!existsSync('.generated')) {
|
||||||
log('📁 Creating .generated directory');
|
log('📁 Creating .generated directory');
|
||||||
mkdirSync('.generated', { recursive: true });
|
mkdirSync('.generated', { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Try to generate test data using the script
|
log('🔄 Attempting to run generate_data.ts...');
|
||||||
log('🔄 Running test data generation script...');
|
|
||||||
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
|
execSync('npx ts-node test-scripts/generate_data.ts', { stdio: 'inherit' });
|
||||||
log('✅ Test data generation script completed');
|
log('✅ Test data generation completed');
|
||||||
|
|
||||||
// Verify the generated files exist
|
// Verify and log generated files content
|
||||||
const requiredFiles = [
|
const requiredFiles = [
|
||||||
'.generated/test-env.json',
|
'.generated/test-env.json',
|
||||||
'.generated/claim_details.json',
|
'.generated/claim_details.json',
|
||||||
'.generated/contacts.json'
|
'.generated/contacts.json'
|
||||||
];
|
];
|
||||||
|
|
||||||
log('🔍 Verifying generated files:');
|
log('\n📝 Verifying generated files:');
|
||||||
for (const file of requiredFiles) {
|
for (const file of requiredFiles) {
|
||||||
if (!existsSync(file)) {
|
if (!existsSync(file)) {
|
||||||
log(`⚠️ Required file ${file} was not generated`);
|
log(`❌ Missing file: ${file}`);
|
||||||
throw new Error(`Required file ${file} was not generated`);
|
|
||||||
} else {
|
} else {
|
||||||
log(`✅ ${file} exists`);
|
const content = readFileSync(file, 'utf8');
|
||||||
|
log(`\n📄 Content of ${file}:`);
|
||||||
|
log(content);
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(content);
|
||||||
|
if (file.includes('test-env.json')) {
|
||||||
|
log('🔑 CONTACT1_DID in test-env:', parsed.CONTACT1_DID);
|
||||||
|
}
|
||||||
|
if (file.includes('contacts.json')) {
|
||||||
|
log('👥 First contact DID:', parsed[0]?.did);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(`❌ Error parsing ${file}:`, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`⚠️ Failed to generate test data: ${error.message}`);
|
log(`\n⚠️ Test data generation failed: ${error.message}`);
|
||||||
log('⚠️ Creating fallback test data...');
|
log('⚠️ Creating fallback test data...');
|
||||||
|
|
||||||
// Create minimal fallback test data
|
// Create fallback data with detailed logging
|
||||||
const fallbackTestEnv = {
|
const fallbackTestEnv = {
|
||||||
"CONTACT1_DID": "did:example:123456789",
|
"CONTACT1_DID": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B",
|
||||||
"APP_URL": "https://app.timesafari.example"
|
"APP_URL": "https://app.timesafari.example"
|
||||||
};
|
};
|
||||||
|
|
||||||
const fallbackClaimDetails = {
|
|
||||||
"claim_id": "claim_12345",
|
|
||||||
"title": "Test Claim",
|
|
||||||
"description": "This is a test claim"
|
|
||||||
};
|
|
||||||
|
|
||||||
const fallbackContacts = [
|
const fallbackContacts = [
|
||||||
{
|
{
|
||||||
"id": "contact1",
|
"id": "contact1",
|
||||||
"name": "Test Contact",
|
"name": "Test Contact",
|
||||||
"did": "did:example:123456789"
|
"did": "did:ethr:0x35A71Ac3fA0A4D5a4903f10F0f7A3ac4034FaB5B"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Use writeFileSync to overwrite any existing files
|
log('\n📝 Writing fallback data:');
|
||||||
const { writeFileSync } = require('fs');
|
log('TestEnv:', JSON.stringify(fallbackTestEnv, null, 2));
|
||||||
|
log('Contacts:', JSON.stringify(fallbackContacts, null, 2));
|
||||||
|
|
||||||
writeFileSync('.generated/test-env.json', JSON.stringify(fallbackTestEnv, null, 2));
|
writeFileSync('.generated/test-env.json', JSON.stringify(fallbackTestEnv, null, 2));
|
||||||
writeFileSync('.generated/claim_details.json', JSON.stringify(fallbackClaimDetails, null, 2));
|
|
||||||
writeFileSync('.generated/contacts.json', JSON.stringify(fallbackContacts, null, 2));
|
writeFileSync('.generated/contacts.json', JSON.stringify(fallbackContacts, null, 2));
|
||||||
|
|
||||||
log('✅ Fallback test data created');
|
// Verify fallback data was written
|
||||||
|
log('\n🔍 Verifying fallback data:');
|
||||||
// Verify files were created
|
try {
|
||||||
const requiredFiles = [
|
const writtenTestEnv = JSON.parse(readFileSync('.generated/test-env.json', 'utf8'));
|
||||||
'.generated/test-env.json',
|
const writtenContacts = JSON.parse(readFileSync('.generated/contacts.json', 'utf8'));
|
||||||
'.generated/claim_details.json',
|
log('Written TestEnv:', writtenTestEnv);
|
||||||
'.generated/contacts.json'
|
log('Written Contacts:', writtenContacts);
|
||||||
];
|
} catch (e) {
|
||||||
|
log('❌ Error verifying fallback data:', e);
|
||||||
log('🔍 Verifying fallback files:');
|
|
||||||
for (const file of requiredFiles) {
|
|
||||||
if (!existsSync(file)) {
|
|
||||||
log(`⚠️ Failed to create ${file}`);
|
|
||||||
} else {
|
|
||||||
log(`✅ Created ${file}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -535,6 +511,96 @@ const runIosApp = async (log, simulator) => {
|
|||||||
log('✅ App launched successfully');
|
log('✅ App launched successfully');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const validateTestData = (log) => {
|
||||||
|
log('\n=== VALIDATING TEST DATA ===');
|
||||||
|
|
||||||
|
const generateFreshTestData = () => {
|
||||||
|
log('\n🔄 Generating fresh test data...');
|
||||||
|
try {
|
||||||
|
// Ensure .generated directory exists
|
||||||
|
if (!existsSync('.generated')) {
|
||||||
|
mkdirSync('.generated', { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the generate_data.ts script synchronously
|
||||||
|
log('Running generate_data.ts...');
|
||||||
|
execSync('npx ts-node test-scripts/generate_data.ts', {
|
||||||
|
stdio: 'inherit',
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Read and validate the generated files
|
||||||
|
const testEnvPath = '.generated/test-env.json';
|
||||||
|
const contactsPath = '.generated/contacts.json';
|
||||||
|
|
||||||
|
if (!existsSync(testEnvPath) || !existsSync(contactsPath)) {
|
||||||
|
throw new Error('Generated files not found after running generate_data.ts');
|
||||||
|
}
|
||||||
|
|
||||||
|
const testEnv = JSON.parse(readFileSync(testEnvPath, 'utf8'));
|
||||||
|
const contacts = JSON.parse(readFileSync(contactsPath, 'utf8'));
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!testEnv.CONTACT1_DID) {
|
||||||
|
throw new Error('CONTACT1_DID missing from generated test data');
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Generated test data:', {
|
||||||
|
testEnv: testEnv,
|
||||||
|
contacts: contacts
|
||||||
|
});
|
||||||
|
|
||||||
|
return { testEnv, contacts };
|
||||||
|
} catch (error) {
|
||||||
|
log('❌ Test data generation failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to read existing data or generate fresh data
|
||||||
|
const testEnvPath = '.generated/test-env.json';
|
||||||
|
const contactsPath = '.generated/contacts.json';
|
||||||
|
|
||||||
|
let testData;
|
||||||
|
|
||||||
|
// If either file is missing or invalid, generate fresh data
|
||||||
|
if (!existsSync(testEnvPath) || !existsSync(contactsPath)) {
|
||||||
|
testData = generateFreshTestData();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const testEnv = JSON.parse(readFileSync(testEnvPath, 'utf8'));
|
||||||
|
const contacts = JSON.parse(readFileSync(contactsPath, 'utf8'));
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if (!testEnv.CLAIM_ID || !testEnv.CONTACT1_DID) {
|
||||||
|
log('⚠️ Existing test data missing required fields, regenerating...');
|
||||||
|
testData = generateFreshTestData();
|
||||||
|
} else {
|
||||||
|
testData = { testEnv, contacts };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log('⚠️ Error reading existing test data, regenerating...');
|
||||||
|
testData = generateFreshTestData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final validation of data
|
||||||
|
if (!testData.testEnv.CLAIM_ID || !testData.testEnv.CONTACT1_DID) {
|
||||||
|
throw new Error('Test data validation failed even after generation');
|
||||||
|
}
|
||||||
|
|
||||||
|
log('✅ Test data validated successfully');
|
||||||
|
log('📄 Test Environment:', JSON.stringify(testData.testEnv, null, 2));
|
||||||
|
|
||||||
|
return testData;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`❌ Test data validation failed: ${error.message}`);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run deeplink tests
|
* Run deeplink tests
|
||||||
* Optionally tests deeplinks if the test data is available
|
* Optionally tests deeplinks if the test data is available
|
||||||
@@ -543,76 +609,65 @@ const runIosApp = async (log, simulator) => {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
const runDeeplinkTests = async (log) => {
|
const runDeeplinkTests = async (log) => {
|
||||||
log('🔗 Starting deeplink tests...');
|
log('\n=== Starting Deeplink Tests ===');
|
||||||
|
|
||||||
// Import readline module for user input
|
// Validate test data before proceeding
|
||||||
const readline = require('readline');
|
let testEnv, contacts;
|
||||||
|
try {
|
||||||
|
({ testEnv, contacts } = validateTestData(log));
|
||||||
|
} catch (error) {
|
||||||
|
log('❌ Cannot proceed with tests due to invalid test data');
|
||||||
|
log(`Error: ${error.message}`);
|
||||||
|
log('Please ensure test data is properly generated before running tests');
|
||||||
|
process.exit(1); // Exit with error code
|
||||||
|
}
|
||||||
|
|
||||||
// Create readline interface
|
// Now we can safely create the deeplink tests knowing we have valid data
|
||||||
const rl = readline.createInterface({
|
const deeplinkTests = [
|
||||||
input: process.stdin,
|
{
|
||||||
output: process.stdout
|
url: `timesafari://claim/${testEnv.CLAIM_ID}`,
|
||||||
});
|
description: 'Claim view'
|
||||||
|
},
|
||||||
// Promisify the question method
|
{
|
||||||
const question = (query) => new Promise(resolve => rl.question(query, resolve));
|
url: `timesafari://claim-cert/${testEnv.CERT_ID || testEnv.CLAIM_ID}`,
|
||||||
|
description: 'Claim certificate view'
|
||||||
// Register URL scheme if needed
|
},
|
||||||
checkAndRegisterUrlScheme(log);
|
{
|
||||||
|
url: `timesafari://claim-add-raw/${testEnv.RAW_CLAIM_ID || testEnv.CLAIM_ID}`,
|
||||||
// Check if test data files exist first
|
description: 'Raw claim addition'
|
||||||
const requiredFiles = [
|
},
|
||||||
'.generated/test-env.json',
|
{
|
||||||
'.generated/claim_details.json',
|
url: 'timesafari://did/test',
|
||||||
'.generated/contacts.json'
|
description: 'DID view with test identifier'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
||||||
|
description: 'DID view with contact DID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: (() => {
|
||||||
|
if (!testEnv?.CONTACT1_DID) {
|
||||||
|
throw new Error('Cannot construct contact-edit URL: CONTACT1_DID is missing');
|
||||||
|
}
|
||||||
|
const url = `timesafari://contact-edit/${testEnv.CONTACT1_DID}`;
|
||||||
|
log('Created contact-edit URL:', url);
|
||||||
|
return url;
|
||||||
|
})(),
|
||||||
|
description: 'Contact editing'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
||||||
|
description: 'Contacts import'
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const file of requiredFiles) {
|
// Log the final test configuration
|
||||||
if (!existsSync(file)) {
|
log('\n5. Final Test Configuration:');
|
||||||
log(`⚠️ Required file ${file} does not exist`);
|
deeplinkTests.forEach((test, i) => {
|
||||||
log('⚠️ Skipping deeplink tests');
|
log(`\nTest ${i + 1}:`);
|
||||||
rl.close();
|
log(`Description: ${test.description}`);
|
||||||
return;
|
log(`URL: ${test.url}`);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Check if our app is actually running in the simulator
|
|
||||||
log('🔍 Checking if app is currently running in simulator...');
|
|
||||||
try {
|
|
||||||
const runningApps = execSync('xcrun simctl listapps booted').toString();
|
|
||||||
const appIdentifier = getAppIdentifier();
|
|
||||||
|
|
||||||
if (!runningApps.includes(appIdentifier)) {
|
|
||||||
log('⚠️ The app does not appear to be running in the simulator.');
|
|
||||||
const shouldLaunch = await question('Would you like to launch the app now? (y/n): ');
|
|
||||||
|
|
||||||
if (shouldLaunch.toLowerCase() === 'y' || shouldLaunch.toLowerCase() === 'yes') {
|
|
||||||
// Try launching the app again
|
|
||||||
log('🚀 Launching app in simulator...');
|
|
||||||
const simulatorInfo = JSON.parse(execSync('xcrun simctl list -j devices booted').toString());
|
|
||||||
const booted = Object.values(simulatorInfo.devices)
|
|
||||||
.flat()
|
|
||||||
.find(device => device.state === 'Booted');
|
|
||||||
|
|
||||||
if (booted) {
|
|
||||||
execSync(`npx cap run ios --target="${booted.udid}"`, { stdio: 'inherit' });
|
|
||||||
log('✅ App launched');
|
|
||||||
} else {
|
|
||||||
log('⚠️ No booted simulator found');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('⚠️ Deeplink tests require the app to be running');
|
|
||||||
log('⚠️ Please launch the app manually and restart the tests');
|
|
||||||
rl.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log('✅ App is running in simulator');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log(`⚠️ Unable to check if app is running: ${error.message}`);
|
|
||||||
log('⚠️ Proceeding with deeplink tests, but they may fail if app is not running');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show instructions for iOS security dialogs
|
// Show instructions for iOS security dialogs
|
||||||
log('\n📱 IMPORTANT: iOS Security Dialog Instructions:');
|
log('\n📱 IMPORTANT: iOS Security Dialog Instructions:');
|
||||||
@@ -627,110 +682,41 @@ const runDeeplinkTests = async (log) => {
|
|||||||
await question('Press Enter when the app is visible and in the foreground...');
|
await question('Press Enter when the app is visible and in the foreground...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Load test data
|
|
||||||
log('📂 Loading test data from .generated directory');
|
|
||||||
let testEnv, claimDetails, contacts;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const testEnvContent = readFileSync('.generated/test-env.json', 'utf8');
|
|
||||||
testEnv = JSON.parse(testEnvContent);
|
|
||||||
log('✅ Loaded test-env.json');
|
|
||||||
} catch (error) {
|
|
||||||
log(`⚠️ Failed to load test-env.json: ${error.message}`);
|
|
||||||
rl.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const claimDetailsContent = readFileSync('.generated/claim_details.json', 'utf8');
|
|
||||||
claimDetails = JSON.parse(claimDetailsContent);
|
|
||||||
log('✅ Loaded claim_details.json');
|
|
||||||
} catch (error) {
|
|
||||||
log(`⚠️ Failed to load claim_details.json: ${error.message}`);
|
|
||||||
rl.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const contactsContent = readFileSync('.generated/contacts.json', 'utf8');
|
|
||||||
contacts = JSON.parse(contactsContent);
|
|
||||||
log('✅ Loaded contacts.json');
|
|
||||||
} catch (error) {
|
|
||||||
log(`⚠️ Failed to load contacts.json: ${error.message}`);
|
|
||||||
rl.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the app URL scheme is registered in the simulator
|
|
||||||
log('🔍 Checking if URL scheme is registered in simulator...');
|
|
||||||
try {
|
|
||||||
// Attempt to open a simple URL with the scheme
|
|
||||||
log('⚠️ A security dialog will appear - Click "Open" to continue');
|
|
||||||
execSync(`xcrun simctl openurl booted "timesafari://test"`, { stdio: 'pipe' });
|
|
||||||
log('✅ URL scheme is registered and working');
|
|
||||||
} catch (error) {
|
|
||||||
const errorMessage = error.message || '';
|
|
||||||
|
|
||||||
// Check for the specific error code that indicates an unregistered URL scheme
|
|
||||||
if (errorMessage.includes('OSStatus error -10814') || errorMessage.includes('NSOSStatusErrorDomain, code=-10814')) {
|
|
||||||
log('⚠️ URL scheme "timesafari://" is not registered in the app or app is not running');
|
|
||||||
log('⚠️ The scheme was added to Info.plist but the app may need to be rebuilt');
|
|
||||||
log('⚠️ Trying to continue with tests, but they may fail');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for user confirmation before proceeding
|
|
||||||
await question('Press Enter to continue with the tests...');
|
|
||||||
|
|
||||||
// Test URLs
|
|
||||||
const deeplinkTests = [
|
|
||||||
{
|
|
||||||
url: `timesafari://claim/${claimDetails.claim_id}`,
|
|
||||||
description: 'Claim view'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: `timesafari://claim-cert/${claimDetails.claim_id}`,
|
|
||||||
description: 'Claim certificate view'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: `timesafari://claim-add-raw/${claimDetails.claim_id}`,
|
|
||||||
description: 'Raw claim addition'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'timesafari://did/test',
|
|
||||||
description: 'DID view with test identifier'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
|
||||||
description: 'DID view with contact DID'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: `timesafari://contact-edit/${testEnv.CONTACT1_DID}`,
|
|
||||||
description: 'Contact editing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
|
||||||
description: 'Contacts import'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Execute each test
|
// Execute each test
|
||||||
let testsCompleted = 0;
|
let testsCompleted = 0;
|
||||||
let testsSkipped = 0;
|
let testsSkipped = 0;
|
||||||
|
|
||||||
for (const test of deeplinkTests) {
|
for (const test of deeplinkTests) {
|
||||||
|
// Show upcoming test info before execution
|
||||||
|
log('\n📱 NEXT TEST:');
|
||||||
|
log('------------------------');
|
||||||
|
log(`Description: ${test.description}`);
|
||||||
|
log(`URL to test: ${test.url}`);
|
||||||
|
log('------------------------');
|
||||||
|
|
||||||
|
// Clear prompt for user action
|
||||||
|
await question('\n⏎ Press Enter to execute this test (or Ctrl+C to quit)...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log(`\n🔗 Testing deeplink: ${test.description}`);
|
log('🚀 Executing deeplink test...');
|
||||||
log(`URL: ${test.url}`);
|
|
||||||
log('⚠️ iOS SECURITY DIALOG WILL APPEAR - Click "Open" to continue');
|
log('⚠️ iOS SECURITY DIALOG WILL APPEAR - Click "Open" to continue');
|
||||||
|
|
||||||
execSync(`xcrun simctl openurl booted "${test.url}"`, { stdio: 'pipe' });
|
execSync(`xcrun simctl openurl booted "${test.url}"`, { stdio: 'pipe' });
|
||||||
log(`✅ Successfully executed: ${test.description}`);
|
log(`✅ Successfully executed: ${test.description}`);
|
||||||
testsCompleted++;
|
testsCompleted++;
|
||||||
|
|
||||||
// Wait for user to press Enter before continuing to next test
|
// Show progress
|
||||||
|
log(`\n📊 Progress: ${testsCompleted}/${deeplinkTests.length} tests completed`);
|
||||||
|
|
||||||
|
// If there are more tests, show the next one
|
||||||
if (testsCompleted < deeplinkTests.length) {
|
if (testsCompleted < deeplinkTests.length) {
|
||||||
await question('Press Enter to continue to the next test...');
|
const nextTest = deeplinkTests[testsCompleted];
|
||||||
|
log('\n⏭️ NEXT UP:');
|
||||||
|
log('------------------------');
|
||||||
|
log(`Next test will be: ${nextTest.description}`);
|
||||||
|
log(`URL: ${nextTest.url}`);
|
||||||
|
log('------------------------');
|
||||||
|
await question('\n⏎ Press Enter when ready for the next test...');
|
||||||
}
|
}
|
||||||
} catch (deeplinkError) {
|
} catch (deeplinkError) {
|
||||||
const errorMessage = deeplinkError.message || '';
|
const errorMessage = deeplinkError.message || '';
|
||||||
@@ -745,14 +731,22 @@ const runDeeplinkTests = async (log) => {
|
|||||||
}
|
}
|
||||||
log('⚠️ Continuing with next test...');
|
log('⚠️ Continuing with next test...');
|
||||||
|
|
||||||
// Wait for user to press Enter before continuing to next test
|
// Show next test info after error handling
|
||||||
if (testsCompleted + testsSkipped < deeplinkTests.length) {
|
if (testsCompleted + testsSkipped < deeplinkTests.length) {
|
||||||
await question('Press Enter to continue to the next test...');
|
const nextTest = deeplinkTests[testsCompleted + testsSkipped];
|
||||||
|
log('\n⏭️ NEXT UP:');
|
||||||
|
log('------------------------');
|
||||||
|
log(`Next test will be: ${nextTest.description}`);
|
||||||
|
log(`URL: ${nextTest.url}`);
|
||||||
|
log('------------------------');
|
||||||
|
await question('\n⏎ Press Enter when ready for the next test...');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`✅ Deeplink tests completed: ${testsCompleted} successful, ${testsSkipped} skipped`);
|
log('\n🎉 All deeplink tests completed!');
|
||||||
|
log(`✅ Successful: ${testsCompleted}`);
|
||||||
|
log(`⚠️ Skipped: ${testsSkipped}`);
|
||||||
|
|
||||||
if (testsSkipped > 0) {
|
if (testsSkipped > 0) {
|
||||||
log('\n📝 Note about skipped tests:');
|
log('\n📝 Note about skipped tests:');
|
||||||
@@ -762,15 +756,9 @@ const runDeeplinkTests = async (log) => {
|
|||||||
log('4. iOS security dialogs must be manually approved for each deeplink test');
|
log('4. iOS security dialogs must be manually approved for each deeplink test');
|
||||||
log('5. If these conditions are met and tests still fail, check URL handling in the app code');
|
log('5. If these conditions are met and tests still fail, check URL handling in the app code');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close readline interface
|
|
||||||
rl.close();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(`❌ Deeplink tests setup failed: ${error.message}`);
|
log(`❌ Deeplink tests setup failed: ${error.message}`);
|
||||||
log('⚠️ Deeplink tests might be unavailable or test data is missing');
|
log('⚠️ Deeplink tests might be unavailable or test data is missing');
|
||||||
// Close readline interface
|
|
||||||
rl.close();
|
|
||||||
// Don't rethrow the error to prevent halting the process
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import {
|
|||||||
deepLinkSchemas,
|
deepLinkSchemas,
|
||||||
baseUrlSchema,
|
baseUrlSchema,
|
||||||
routeSchema,
|
routeSchema,
|
||||||
|
DeepLinkRoute,
|
||||||
} from "../types/deepLinks";
|
} from "../types/deepLinks";
|
||||||
import { logConsoleAndDb } from "../db";
|
import { logConsoleAndDb } from "../db";
|
||||||
import type { DeepLinkError } from "../interfaces/deepLinks";
|
import type { DeepLinkError } from "../interfaces/deepLinks";
|
||||||
@@ -120,12 +121,12 @@ export class DeepLinkHandler {
|
|||||||
"invite-one-accept": "invite-one-accept",
|
"invite-one-accept": "invite-one-accept",
|
||||||
"contact-import": "contact-import",
|
"contact-import": "contact-import",
|
||||||
"confirm-gift": "confirm-gift",
|
"confirm-gift": "confirm-gift",
|
||||||
claim: "claim",
|
"claim": "claim",
|
||||||
"claim-cert": "claim-cert",
|
"claim-cert": "claim-cert",
|
||||||
"claim-add-raw": "claim-add-raw",
|
"claim-add-raw": "claim-add-raw",
|
||||||
"contact-edit": "contact-edit",
|
"contact-edit": "contact-edit",
|
||||||
contacts: "contacts",
|
"contacts": "contacts",
|
||||||
did: "did",
|
"did": "did",
|
||||||
};
|
};
|
||||||
|
|
||||||
// First try to validate the route path
|
// First try to validate the route path
|
||||||
|
|||||||
@@ -1,10 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="deep-link-error">
|
<div class="deep-link-error">
|
||||||
|
<div class="safe-area-spacer"></div>
|
||||||
<h1>Invalid Deep Link</h1>
|
<h1>Invalid Deep Link</h1>
|
||||||
<div class="error-details">
|
<div class="error-details">
|
||||||
|
<div class="error-message">
|
||||||
|
<h3>Error Details</h3>
|
||||||
<p>{{ errorMessage }}</p>
|
<p>{{ errorMessage }}</p>
|
||||||
|
<div v-if="errorCode" class="error-code">
|
||||||
|
Error Code: <span>{{ errorCode }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="originalPath" class="original-link">
|
<div v-if="originalPath" class="original-link">
|
||||||
<strong>Link attempted:</strong> timesafari://{{ originalPath }}
|
<h3>Attempted Link</h3>
|
||||||
|
<code>timesafari://{{ formattedPath }}</code>
|
||||||
|
<div class="debug-info">
|
||||||
|
<h4>Parameters:</h4>
|
||||||
|
<pre>{{ JSON.stringify(route.params, null, 2) }}</pre>
|
||||||
|
<h4>Query:</h4>
|
||||||
|
<pre>{{ JSON.stringify(route.query, null, 2) }}</pre>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
@@ -17,7 +31,7 @@
|
|||||||
<h2>Supported Deep Links</h2>
|
<h2>Supported Deep Links</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="route in validRoutes" :key="route">
|
<li v-for="route in validRoutes" :key="route">
|
||||||
timesafari://{{ route }}/:id
|
<code>timesafari://{{ route }}/:id</code>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,6 +59,19 @@ const errorMessage = computed(
|
|||||||
const originalPath = computed(() => route.query.originalPath as string);
|
const originalPath = computed(() => route.query.originalPath as string);
|
||||||
const validRoutes = VALID_DEEP_LINK_ROUTES;
|
const validRoutes = VALID_DEEP_LINK_ROUTES;
|
||||||
|
|
||||||
|
// Format the path and include any parameters
|
||||||
|
const formattedPath = computed(() => {
|
||||||
|
if (!originalPath.value) return '';
|
||||||
|
let path = originalPath.value.replace(/^\/+/, '');
|
||||||
|
|
||||||
|
// Log for debugging
|
||||||
|
console.log('Original Path:', originalPath.value);
|
||||||
|
console.log('Route Params:', route.params);
|
||||||
|
console.log('Route Query:', route.query);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
});
|
||||||
|
|
||||||
// Navigation methods
|
// Navigation methods
|
||||||
const goHome = () => router.replace({ name: "home" });
|
const goHome = () => router.replace({ name: "home" });
|
||||||
const reportIssue = () => {
|
const reportIssue = () => {
|
||||||
@@ -60,8 +87,144 @@ const reportIssue = () => {
|
|||||||
// Log the error for analytics
|
// Log the error for analytics
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
logConsoleAndDb(
|
logConsoleAndDb(
|
||||||
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}`,
|
`[DeepLink] Error page displayed for path: ${originalPath.value}, code: ${errorCode.value}, params: ${JSON.stringify(route.params)}`,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.deep-link-error {
|
||||||
|
padding-top: 60px;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.safe-area-spacer {
|
||||||
|
height: env(safe-area-inset-top);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #ff4444;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-details {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message p {
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-code span {
|
||||||
|
background-color: #eee;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.original-link {
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.original-link code {
|
||||||
|
color: #0066cc;
|
||||||
|
font-family: monospace;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin: 24px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: none;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button {
|
||||||
|
background-color: #007aff;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-button {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supported-links {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supported-links ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supported-links li {
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.supported-links code {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #0066cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-info {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-info h4 {
|
||||||
|
margin: 8px 0;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-info pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #333;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user