integration/js/utils/SdkBaseTest.js (318 lines of code) (raw):

const { KiteBaseTest, TestUtils } = require('../node_modules/kite-common'); const { AllureTestReport } = require('../node_modules/kite-common/report'); const { SetTestBrokenStep } = require('../steps'); const { SaucelabsSession } = require('./WebdriverSauceLabs'); const { BrowserStackSession } = require('./WebdriverBrowserStack'); const { LocalSession } = require('./WebdriverLocal'); const { emitMetric } = require('./CloudWatch'); const { v4: uuidv4 } = require('uuid'); const fs = require('fs'); class SdkBaseTest extends KiteBaseTest { constructor(name, kiteConfig, testName) { super(name, kiteConfig); this.baseUrl = this.url; if (testName === 'ContentShareOnlyAllowTwoTest') { this.url = this.getTransformedURL(this.url, 'max-content-share', 'true'); } if (testName === 'MessagingSessionTest') { this.userArn = this.payload.userArn; } if (testName === 'MediaCapture' || testName === 'Transcription') { this.region = this.payload.region; } if (['Video', 'Audio'].includes(testName)) { this.url = this.getTransformedURL(this.url, 'attendee-presence-timeout-ms', 5000); // Allows us to easily treat unexpected reconnects as failures this.url = this.getTransformedURL(this.url, 'abort-on-reconnect', 'true'); this.url = this.getTransformedURL(this.url, 'fatal', '1'); } this.originalURL = this.url; this.testReady = false; this.testFinish = false; if(kiteConfig.capabilities.platform == 'IOS' || kiteConfig.capabilities.platform == 'ANDROID') { this.testName = 'Mobile_'+testName; } else { this.testName = testName; } this.useSimulcast = !!this.payload.useSimulcast; this.cwNamespaceInfix = this.payload.cwNamespaceInfix || ''; if (this.useSimulcast) { this.testName += 'Simulcast'; } this.useVideoProcessor = !!this.payload.useVideoProcessor; if (this.useVideoProcessor) { this.testName += 'Processor'; } if(process.env.STAGE !== undefined) { if(this.cwNamespaceInfix !== '') { this.capabilities['name'] = `${this.testName}-${this.cwNamespaceInfix}-${process.env.TEST_TYPE}-${process.env.STAGE}`; } else { this.capabilities['name'] = `${this.testName}-${process.env.TEST_TYPE}-${process.env.STAGE}`; } } else { if(this.cwNamespaceInfix !== '') { this.capabilities['name'] = `${this.testName}-${this.cwNamespaceInfix}-${process.env.TEST_TYPE}`; } else { this.capabilities['name'] = `${this.testName}-${process.env.TEST_TYPE}`; } } this.seleniumSessions = []; this.timeout = this.payload.testTimeout ? this.payload.testTimeout : 60; if (this.numberOfParticipant > 1) { this.io.emit('test_name', this.testName); this.io.emit('test_capabilities', this.capabilities); this.io.on('all_clients_ready', isReady => { this.testReady = !!isReady; }); this.io.on('remote_video_on', (id) => { console.log(`[${id}] turned on video`); this.numVideoRemoteOn += 1; this.numVideoRemoteOff = Math.max(1, this.numVideoRemoteOff - 1); }); this.io.on('remote_video_off', (id) => { console.log(`[${id}] turned off video`); this.numVideoRemoteOff += 1; this.numVideoRemoteOn = Math.max(1, this.numVideoRemoteOn - 1); }); this.io.on('video_check_completed_by_other_participants', (id) => { console.log(`[${id}] completed video checks`); this.numOfParticipantsCompletedVideoCheck += 1; }); this.io.on('audio_check_completed_by_other_participants', (id) => { console.log(`[${id}] completed audio checks`); this.numOfParticipantsCompletedAudioCheck += 1; }); this.io.on('remote_audio_on', (id) => { console.log(`[${id}] turned on audio`); this.numRemoteAudioOn += 1; this.numRemoteAudioOff = Math.max(1, this.numRemoteAudioOff - 1); }); this.io.on('remote_audio_off', (id) => { console.log(`[${id}] turned off audio`); this.numRemoteAudioOff += 1; this.numRemoteAudioOn = Math.max(1, this.numRemoteAudioOn - 1); }); this.io.on('failed', () => { console.log('[OTHER_PARTICIPANT] test failed, quitting...'); this.remoteFailed = true; }); this.io.on('participant_count', count => { console.log('Number of participants on the meeting: ' + count); this.numRemoteJoined = count; }); this.io.on('meeting_created', meetingId => { this.meetingCreated = true; this.meetingTitle = meetingId; this.url = this.getTransformedURL(this.originalURL, 'm', this.meetingTitle); }); this.io.on('finished', () => { this.testFinish = true; }); } } initializeState() { this.failedTest = false; this.numVideoRemoteOn = 1; this.numVideoRemoteOff = 1; this.numOfParticipantsCompletedVideoCheck = 1; this.numOfParticipantsCompletedAudioCheck = 1; this.numRemoteAudioOn = 1; this.numRemoteAudioOff = 1; this.remoteFailed = false; this.numRemoteJoined = 0; this.meetingCreated = false; //Reset the status so KITE does not skip all the steps in next run this.report = new AllureTestReport(this.name); if (this.io !== undefined) { this.attendeeId = uuidv4(); console.log('attendee id generated: ' + this.attendeeId); this.io.emit('setup_test', this.baseUrl, this.attendeeId); } else { this.meetingTitle = uuidv4(); this.url = this.getTransformedURL(this.originalURL, 'm', this.meetingTitle); } } async createSeleniumSession(capabilities) { if (process.env.SELENIUM_GRID_PROVIDER === 'browserstack') { const session = await BrowserStackSession.createSession(capabilities, this.getAppName()); return session; } else if (process.env.SELENIUM_GRID_PROVIDER === 'local') { const session = await LocalSession.createSession(capabilities, this.remoteUrl, this.getAppName()); return session; } else { const invalidSessionIdRegEx = new RegExp(/^new_request:/); for (let i = 0; i < 3; i++) { const session = await SaucelabsSession.createSession(capabilities, this.getAppName()); const sessionId = await session.getSessionId(); if (invalidSessionIdRegEx.test(sessionId)) { console.log(`Invalid Saucelabs session id : ${sessionId}. Retrying: ${i + 1}`); await new Promise(r => setTimeout(r, 1000)); } else { console.log(`Successfully created a Saucelabs session: ${sessionId}`); if (this.isMobilePlatform()) { this.capabilities.deviceName = await session.getDeviceName(); console.log(`Using device: ${this.capabilities.deviceName}`); const testMobileURL = await session.getMobileTestRunURL(); console.log(`Test report URL: ${testMobileURL}`); } return session; } } throw new Error('Failed to create Selenium session'); } } async initializeSeleniumSession(numberOfSeleniumSessions) { console.log(`Provisioning ${numberOfSeleniumSessions} sessions on ${process.env.SELENIUM_GRID_PROVIDER}`); this.seleniumSessions = []; for (let i = 0; i < numberOfSeleniumSessions; i++) { try { const session = await this.createSeleniumSession(this.capabilities); await session.init(); this.seleniumSessions.push(session); } catch (e) { console.log('Failed to initialize'); console.log(e); await this.updateSeleniumTestResult(false); await this.quitSeleniumSessions(); await emitMetric('Common', this.capabilities, 'SeleniumInit', 0, this.cwNamespaceInfix); throw (e); } } emitMetric('Common', this.capabilities, 'SeleniumInit', 1, this.cwNamespaceInfix); return true; } numberOfSessions() { if (this.payload.seleniumSessions && (this.payload.seleniumSessions[this.capabilities.browserName])) { return this.payload.seleniumSessions[this.capabilities.browserName]; } if (this.payload.seleniumSessions && (this.payload.seleniumSessions[this.capabilities.platform])) { return this.payload.seleniumSessions[this.capabilities.platform]; } return 1; } writeCompletionTimeTo(filePath) { try { const epochTimeInSeconds = Math.round(Date.now() / 1000); fs.appendFileSync(`${filePath}/last_run_timestamp`, `${epochTimeInSeconds}\n`, { flag: 'a+' }); console.log(`Wrote canary completion timestamp : ${epochTimeInSeconds}`); } catch (e) { console.log(`Failed to write last completed canary timestamp to a file : ${e}`); } } async testScript() { const maxRetries = this.payload.retry === undefined || this.payload.retry < 1 ? 5 : this.payload.retry; const numberOfSeleniumSessions = this.numberOfSessions(); let retryCount = 0; while (retryCount < maxRetries) { if (retryCount !== 0) { console.log(`Retrying : ${retryCount}`); } try { if (!await this.initializeSeleniumSession(numberOfSeleniumSessions)) { if (this.testName === 'MediaCapture' || this.testName === 'Transcription') { await emitMetric(this.testName, this.capabilities, 'E2E_' + this.region, 0, this.cwNamespaceInfix); } else { await emitMetric(this.testName, this.capabilities, 'E2E', 0, this.cwNamespaceInfix); } return; } //Wait for other to be ready if (this.numberOfParticipant > 1 && this.io) { this.io.emit('test_ready', true); await this.waitForTestReady(); if (!this.testReady) { this.io.emit('test_ready', false); console.log('[OTHER_PARTICIPANT] failed to be ready'); this.remoteFailed = true; return; } } this.testFinish = false; this.initializeState(); console.log('Running test on: ' + process.env.SELENIUM_GRID_PROVIDER); await this.runIntegrationTest(); } catch (e) { console.error(e); this.failedTest = true; await SetTestBrokenStep.executeStep(this, 'Error exception when running test'); } finally { await this.closeCurrentTest(!this.failedTest && !this.remoteFailed); } if (this.payload.canaryLogPath !== undefined) { this.writeCompletionTimeTo(this.payload.canaryLogPath); } // Retry if the local or remote test failed if (!this.failedTest && !this.remoteFailed) { break; } if (this.numberOfParticipant > 1 && this.io) { this.io.emit('test_ready', false); if (!this.testFinish) { console.log('[OTHER_PARTICIPANT] timed out'); break; } } retryCount++; } } async runIntegrationTest() { } async waitForTestReady() { const maxWaitTime = 60 * 1000; //1 min since SauceLabs timeout after 90s const interval = 100; let waitTime = 0; while (waitTime < maxWaitTime) { if (this.testReady) { return; } console.log(`Waiting for others: ${waitTime}`); waitTime += interval; await TestUtils.waitAround(interval); } } async updateSeleniumTestResult(testResult) { for (let i = 0; i < this.seleniumSessions.length; i++) { await this.seleniumSessions[i].updateTestResults(testResult); } } async quitSeleniumSessions() { for (let i = 0; i < this.seleniumSessions.length; i++) { await this.seleniumSessions[i].quit(); } } async printRunDetails(testResult) { for (let i = 0; i < this.seleniumSessions.length; i++) { await this.seleniumSessions[i].printRunDetails(testResult); } } async closeCurrentTest(testResult) { try { await this.updateSeleniumTestResult(testResult); if (this.testName === 'MediaCapture' || this.testName === 'Transcription') { await emitMetric(this.testName, this.capabilities, 'E2E_' + this.region, testResult ? 1 : 0, this.cwNamespaceInfix); } else { await emitMetric(this.testName, this.capabilities, 'E2E', testResult ? 1 : 0, this.cwNamespaceInfix); } await this.printRunDetails(testResult); } catch (e) { console.log(e); } finally { await this.quitSeleniumSessions(); } } isMobilePlatform() { return this.capabilities.platform === 'ANDROID' || this.capabilities.platform === 'IOS'; } getAppName() { if (this.testName && this.testName.toLowerCase().includes('meetingreadinesschecker')) { return 'meetingReadinessChecker'; } else if (this.testName && this.testName.toLowerCase().includes('messagingsession')) { return 'messagingSession'; } else if (this.testName && this.testName.toLowerCase().includes('testapp')) { return 'testApp'; } else { return 'meeting'; }; } getTransformedURL = (url, key, value) => { const sep = url.includes('?') ? '&' : '?'; return `${url}${sep}${encodeURIComponent(key)}=${encodeURIComponent(value)}`; }; } module.exports = SdkBaseTest;