diff --git a/.gitignore b/.gitignore index 1483bdb..ce824d8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,6 @@ dist/ .DS_Store .env drowser +drowser.json drowser-reports.json -/node_modules \ No newline at end of file +/node_modules diff --git a/drowser.json b/drowser.json index 1d3fb83..bfbedc3 100644 --- a/drowser.json +++ b/drowser.json @@ -1,3 +1,3 @@ { - "url": "http://localhost:3000" + "url": "http://localhost:3000" } diff --git a/examples/basic.ts b/examples/basic.ts index 809dc75..96fb52b 100644 --- a/examples/basic.ts +++ b/examples/basic.ts @@ -1,27 +1,6 @@ import { driver } from '../mod.ts' -driver({ browser: 'chrome' }) - .then(({ service }) => { - service.cases = [ - { - name: 'Verify Failed Title', - fn: async ({ builder, assert }) => { - const title = await builder.getTitle() - assert.assertEquals(title, 'Drowsers') - }, - }, - { - name: 'Verify Title', - fn: async ({ builder, assert }) => { - const title = await builder.getTitle() - assert.assertEquals(title, 'Drowser') - }, - }, - ] - }) - .catch((error) => console.log(error)) - -driver({ browser: 'firefox' }) +driver({ browser: 'safari' }) .then(({ service }) => { service.cases = [ { diff --git a/mod.ts b/mod.ts index 28a4dbc..6a9b100 100644 --- a/mod.ts +++ b/mod.ts @@ -1,4 +1,4 @@ -import driver from './src/driver.ts' +import driver from './src/driver/index.ts' import type Types from './src/types.ts' export { driver } diff --git a/src/driver.ts b/src/driver.ts deleted file mode 100644 index f7cce97..0000000 --- a/src/driver.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { assert, Builder, By, isEmpty, join, Kia } from '../deps.ts' -import type { - CaseFn, - ConfigJSON, - Data, - DriverParams, - DriverServiceCaseParamsBuilder, - DrowserDriverResponse, - DrowserServiceCase, - DrowserThenableWebDriver, -} from './types.ts' -import { isValidHttpUrl, result as resultData } from './utils.ts' -import { - caseStatus, - driverBrowserList, - driverBrowsers, - seleniumExceptions, -} from './constants.ts' -import { exportGeneratedLog, exportJSONReport } from './export.ts' - -const driver = async ({ - browser, -}: DriverParams): Promise => { - const data: Data = { url: '', results: [] } - const configPath = join(Deno.cwd(), 'drowser.json') - - try { - await Deno.stat(configPath) - const { url }: ConfigJSON = JSON.parse( - await Deno.readTextFile(configPath), - ) - - if (isEmpty(url) || !isValidHttpUrl({ url })) { - throw new Error( - 'An error occurred, please provide a valid url in drowser config', - ) - } - - data.url = url - } catch (error) { - if (error instanceof Deno.errors.NotFound) { - throw new Error( - 'An error occurred, please create drowser.json file.', - ) - } - - if (!(error instanceof Deno.errors.NotFound)) { - throw new Error( - 'An error occurred, please provide a valid url drowser.json file.', - ) - } - } - - if (isEmpty(browser) || !driverBrowserList.includes(browser)) { - throw new Error( - 'An error occurred, please provide a valid browser driver', - ) - } - - return new Promise((resolve, reject) => { - if (isEmpty(data.url) || !isValidHttpUrl({ url: data.url })) reject() - - const builder = new Builder() - .forBrowser(driverBrowsers[browser]) - .build() as DrowserThenableWebDriver - - const service = { cases: [] } - - const kia = new Kia('Processing your tests') - kia.start() - - builder - .get(data.url) - .then(() => resolve({ service })) - .catch((error: Record) => { - kia.fail('An error occurred while running tests') - reject(seleniumExceptions[error.name]) - }) - .finally(() => { - const methodPromises: Promise[] = [] - - service.cases.forEach((c: DrowserServiceCase) => { - if (typeof c === 'object') { - const omitedBuilder = - builder as unknown as DriverServiceCaseParamsBuilder - const megaBuilder = { - builder: omitedBuilder, - assert, - by: By, - } - const method = c.fn as CaseFn - const methodPromise = method(megaBuilder) - - const start = performance.now() - - methodPromise - .then(() => { - const end = performance.now() - data.results.push( - resultData({ - name: c.name, - status: caseStatus.passed, - timestamp: new Date(), - duration: end - start, - browser, - }), - ) - }) - .catch(() => { - const end = performance.now() - data.results.push( - resultData({ - name: c.name, - status: caseStatus.failed, - duration: end - start, - browser, - }), - ) - }) - - methodPromises.push(methodPromise) - } - }) - - const exportGeneratedFiles = () => { - exportGeneratedLog({ results: data.results }) - exportJSONReport({ - results: data.results, - browser, - }) - } - - Promise.allSettled(methodPromises) - .catch((error) => reject(error)) - .finally(() => { - exportGeneratedFiles() - kia.succeed(`All tests completed on ${browser}`) - builder.quit() - }) - }) - }) -} - -export default driver diff --git a/src/driver/config.ts b/src/driver/config.ts new file mode 100644 index 0000000..4b6bcd3 --- /dev/null +++ b/src/driver/config.ts @@ -0,0 +1,28 @@ +import { isEmpty, join } from '../../deps.ts' +import { isValidHttpUrl } from '../utils.ts' + +export const getConfig = async (): Promise<{ url: string }> => { + const configPath = join(Deno.cwd(), 'drowser.json') + + try { + await Deno.stat(configPath) + + const { url } = JSON.parse(await Deno.readTextFile(configPath)) + + if (isEmpty(url) || !isValidHttpUrl({ url })) { + throw new Error( + 'An error occurred, please provide a valid url in drowser config', + ) + } + + return { url } + } catch (error) { + if (error instanceof Deno.errors.NotFound) { + throw new Error('An error occurred, please create drowser.json file.') + } + + throw new Error( + 'An error occurred, please provide a valid url drowser.json file.', + ) + } +} diff --git a/src/export.ts b/src/driver/export.ts similarity index 93% rename from src/export.ts rename to src/driver/export.ts index 14a1932..52a36a1 100644 --- a/src/export.ts +++ b/src/driver/export.ts @@ -6,7 +6,7 @@ import { readJsonSync, writeJson, writeJsonSync, -} from '../deps.ts' +} from '../../deps.ts' import { generateFileName, getAverageDuration, @@ -14,7 +14,7 @@ import { getCurrentMonth, getFlaky, updateOrCreate, -} from './utils.ts' +} from '../utils.ts' import { DataPoint, DataResult, @@ -22,7 +22,7 @@ import { MonthCount, MonthValue, ReportSchema, -} from './types.ts' +} from '../types.ts' const exportGeneratedLog = ( { results }: { results: Array }, @@ -40,8 +40,7 @@ const exportGeneratedLog = ( const writeResult = () => results.forEach((r) => { - const logRow = - `[${r.timestamp}] - Test with ${r.name} is ${r.status}` + const logRow = `[${r.timestamp}] - Test with ${r.name} is ${r.status}` Deno.writeTextFile(logFilePath, `${logRow}\n`, { append: true }) }) @@ -84,12 +83,11 @@ const exportJSONReport = ( } } - const flatedTotalTests = - jsonData.drowser.metadata.current_month === month - ? jsonData.drowser.cases.flatMap((item) => item.cases).filter(( - c, - ) => c.month_of_test === month) - : [] + const flatedTotalTests = jsonData.drowser.metadata.current_month === month + ? jsonData.drowser.cases.flatMap((item) => item.cases).filter(( + c, + ) => c.month_of_test === month) + : [] const totalTests = [ ...flatedTotalTests, ...results, diff --git a/src/driver/index.ts b/src/driver/index.ts new file mode 100644 index 0000000..7275941 --- /dev/null +++ b/src/driver/index.ts @@ -0,0 +1,81 @@ +import { assert, Builder, By, isEmpty, Kia } from '../../deps.ts' +import type { + Data, + DriverParams, + DrowserDriverResponse, + DrowserServiceCase, + DrowserThenableWebDriver, +} from '../types.ts' +import { isValidHttpUrl } from '../utils.ts' +import { driverBrowsers, seleniumExceptions } from '../constants.ts' +import { exportGeneratedLog, exportJSONReport } from './export.ts' +import { getConfig } from './config.ts' +import { validateParams } from './validate.ts' +import { processServiceCase } from './process.ts' + +const driver = async ({ + browser, +}: DriverParams): Promise => { + const data: Data = { url: '', results: [] } + + const config = await getConfig() + data.url = config.url + + validateParams({ browser }) + + return new Promise((resolve, reject) => { + if (isEmpty(data.url) || !isValidHttpUrl({ url: data.url })) reject() + + const builder = new Builder() + .forBrowser(driverBrowsers[browser]) + .build() as DrowserThenableWebDriver + + const service = { cases: [] } + + const kia = new Kia('Processing your tests') + kia.start() + + builder + .get(data.url) + .then(() => resolve({ service })) + .catch((error: Record) => { + kia.fail('An error occurred while running tests') + reject(seleniumExceptions[error.name]) + }) + .finally(() => { + const methodPromises: Promise[] = [] + + service.cases.forEach((serviceCase: DrowserServiceCase) => { + if (typeof serviceCase === 'object') { + const methodPromise = processServiceCase( + serviceCase, + builder, + assert, + By, + browser, + data, + ) + methodPromises.push(methodPromise) + } + }) + + const exportGeneratedFiles = () => { + exportGeneratedLog({ results: data.results }) + exportJSONReport({ + results: data.results, + browser, + }) + } + + Promise.allSettled(methodPromises) + .catch((error) => reject(error)) + .finally(() => { + exportGeneratedFiles() + kia.succeed(`All tests completed on ${browser}`) + builder.quit() + }) + }) + }) +} + +export default driver diff --git a/src/driver/process.ts b/src/driver/process.ts new file mode 100644 index 0000000..8a96e0b --- /dev/null +++ b/src/driver/process.ts @@ -0,0 +1,64 @@ +import { assert, By, ThenableWebDriver } from '../../deps.ts' +import type { + CaseFn, + Data, + DriverBrowser, + DriverServiceCaseParamsBuilder, + DrowserServiceCase, +} from '../types.ts' +import { result } from '../utils.ts' +import { caseStatus } from '../constants.ts' + +type ByType = typeof By +type AssertType = typeof assert + +export const processServiceCase = ( + serviceCase: DrowserServiceCase, + builder: ThenableWebDriver, + assert: AssertType, + By: ByType, + browser: DriverBrowser, + data: Data, +): Promise => { + return new Promise((resolve, reject) => { + const omitedBuilder = builder as unknown as DriverServiceCaseParamsBuilder + const megaBuilder = { + builder: omitedBuilder, + assert, + by: By, + } + const method = serviceCase.fn as CaseFn + const methodPromise = method(megaBuilder) + + const start = performance.now() + + methodPromise + .then(() => { + const end = performance.now() + data.results.push( + result({ + name: serviceCase.name, + status: caseStatus.passed, + timestamp: new Date(), + duration: end - start, + browser, + }), + ) + + resolve() + }) + .catch(() => { + const end = performance.now() + data.results.push( + result({ + name: serviceCase.name, + status: caseStatus.failed, + duration: end - start, + browser, + }), + ) + + reject() + }) + }) +} diff --git a/src/driver/validate.ts b/src/driver/validate.ts new file mode 100644 index 0000000..3f50cb2 --- /dev/null +++ b/src/driver/validate.ts @@ -0,0 +1,11 @@ +import { driverBrowserList } from '../constants.ts' +import { DriverParams } from '../types.ts' +import { isEmpty } from '../../deps.ts' + +export const validateParams = (params: DriverParams): void => { + const { browser } = params + + if (isEmpty(browser) || !driverBrowserList.includes(browser)) { + throw new Error('An error occurred, please provide a valid browser driver') + } +}