Best JavaScript code snippet using best
output.ts
Source:output.ts
1/*2 * Copyright (c) 2019, salesforce.com, inc.3 * All rights reserved.4 * SPDX-License-Identifier: MIT5 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT6*/7import Table from 'cli-table3';8import chalk from 'chalk';9import Histogram from './histogram';10import {11 BenchmarkMetricNames,12 BenchmarkResultsSnapshot,13 EnvironmentConfig, StatsNode,14 BenchmarkComparison, ResultComparison,15 StatsResults16} from "@best/types";17import { OutputStream } from '@best/console-stream';18interface OutputConfig {19 outputHistograms?: boolean;20}21/*22 * The Output module can write a report or a comparison to a given stream based on a configuration.23 */24export default class Output {25 config: OutputConfig;26 stream: OutputStream;27 constructor(config: OutputConfig, stream: OutputStream) {28 this.config = config || {};29 this.stream = stream;30 }31 /*32 * Show a report for a given set of results.33 */34 report(results: BenchmarkResultsSnapshot[]) {35 results.forEach((result: BenchmarkResultsSnapshot) => {36 const { benchmarkInfo: { benchmarkName }, stats, projectConfig: { benchmarkOutput: benchmarkFolder } } = result;37 // Stats table.38 this.writeStats(benchmarkName, benchmarkFolder, stats!);39 // OS & Browser.40 this.writeEnvironment(result.environment);41 // Optional histogram for each line in the stats table.42 if (this.config.outputHistograms) {43 this.writeHistograms(stats!);44 }45 });46 }47 /*48 * Write a table of statistics for a single benchmark file.49 */50 writeStats(benchmarkName: string, resultsFolder: string, stats: StatsResults) {51 const table = new Table({52 head: ['Benchmark name', 'Metric (ms)', 'N', 'Mean ± StdDev', 'Median ± MAD'],53 style: { head: ['bgBlue', 'white'] },54 });55 this.generateRows(table, stats.results);56 this.stream.write(57 [58 chalk.bold.dim('\n Benchmark results for ') + chalk.bold.magentaBright(benchmarkName),59 chalk.italic(' ' + resultsFolder + '/'),60 table.toString() + '\n',61 ].join('\n'),62 );63 }64 /*65 * Write browser and CPU load information.66 */67 writeEnvironment({ browser, container }: EnvironmentConfig) {68 const cpuLoad = container.load.cpuLoad;69 const loadColor = cpuLoad < 10 ? 'green' : cpuLoad < 50 ? 'yellow' : 'red';70 this.stream.write(' ');71 this.stream.write(72 [73 'Browser version: ' + chalk.bold(browser.version),74 `Benchmark CPU load: ${chalk.bold[loadColor](cpuLoad.toFixed(3) + '%')}`,75 ].join('\n '),76 );77 this.stream.write('\n\n');78 }79 /*80 * Write a set of histograms for a tree of benchmarks.81 */82 writeHistograms(benchmarks: any, parentPath: string = '') {83 // const metricPattern = this.config.outputMetricPattern;84 // const histogramPattern = this.config.outputHistogramPattern;85 benchmarks.forEach((benchmark: any) => {86 const path = `${parentPath}${benchmark.name}`;87 const children = benchmark.benchmarks;88 if (children) {89 this.writeHistograms(children, `${path} > `);90 } else {91 // if (!histogramPattern.test(path)) {92 // return;93 // }94 Object.keys(benchmark).forEach(metric => {95 // if (!metricPattern.test(metric)) {96 // return;97 // }98 const stats = benchmark[metric];99 if (stats && stats.sampleSize) {100 const { samples } = stats;101 const histogram = new Histogram(samples, this.config);102 const plot = histogram.toString();103 this.stream.write(`\n${path} > ${metric}\n${plot}\n\n`);104 }105 });106 }107 });108 }109 /*110 * Recursively populate rows of statistics into a table for a tree of benchmarks.111 */112 generateRows(table: any, benchmarks: StatsNode[], level = 0) {113 // const metricPattern = //this.config.outputMetricPattern;114 benchmarks.forEach((benchmarkNode: StatsNode) => {115 const name = benchmarkNode.name;116 // Root benchmark117 if (benchmarkNode.type === "benchmark") {118 Object.keys(benchmarkNode.metrics).forEach((metric: string) => {119 const metricsStats = benchmarkNode.metrics[metric as BenchmarkMetricNames];120 const metricValues = metricsStats && metricsStats.stats;121 if (metricValues && metricValues.sampleSize) {122 const { sampleSize, mean, median, variance, medianAbsoluteDeviation } = metricValues;123 table.push([124 padding(level) + name,125 chalk.bold(metric),126 sampleSize,127 `${mean.toFixed(3)}` + chalk.gray(` ± ${(Math.sqrt(variance) / mean * 100).toFixed(1)}%`),128 `${median.toFixed(3)}` + chalk.gray(` ± ${(medianAbsoluteDeviation / median * 100).toFixed(1)}%`),129 ]);130 }131 });132 // Group133 } else if (benchmarkNode.type === "group") {134 const emptyFields = Array.apply(null, Array(4)).map(() => '-');135 table.push([padding(level) + name, ...emptyFields]);136 this.generateRows(table, benchmarkNode.nodes, level + 1);137 }138 });139 }140 /*141 * Show a comparison for a pair of commits.142 */143 compare(result: BenchmarkComparison) {144 const { baseCommit, targetCommit } = result;145 type GroupedTables = {146 [projectName: string]: Table[]147 }148 const tables: GroupedTables = result.comparisons.reduce((tables, node): GroupedTables => {149 if (node.type === "project" || node.type === "group") {150 const group = node.comparisons.map(child => {151 return this.generateComparisonTable(baseCommit, targetCommit, child);152 })153 return {154 ...tables,155 [node.name]: group156 }157 }158 return tables;159 }, <GroupedTables>{})160 const flattenedTables = Object.keys(tables).reduce((groups, projectName): string[] => {161 const stringifiedTables = tables[projectName].map(t => t.toString() + '\n');162 const colorProjectName = chalk.bold.dim(projectName);163 groups.push(`\nProject: ${colorProjectName}\n`);164 groups.push(...stringifiedTables);165 return groups;166 }, <string[]>[])167 this.stream.write(flattenedTables.join(''));168 }169 /*170 * Get a comparison table for two different commits.171 */172 generateComparisonTable(baseCommit: string, targetCommit: string, stats: ResultComparison) {173 const benchmark = stats.name.replace('.benchmark', '');174 const table = new Table({175 head: [`Benchmark: ${benchmark}`, `base (${baseCommit})`, `target (${targetCommit})`, 'trend'],176 style: {head: ['bgBlue', 'white']}177 });178 this.generateComparisonRows(table, stats);179 return table;180 }181 /*182 * Recursively populate rows into a table for a tree of comparisons.183 */184 generateComparisonRows(table: Table, stats: ResultComparison, groupName = '') {185 if (stats.type === "project" || stats.type === "group") {186 stats.comparisons.forEach(node => {187 if (node.type === "project" || node.type === "group") {188 const name = node.name;189 this.generateComparisonRows(table, node, name);190 } else if (node.type === "benchmark") {191 // // row with benchmark name192 const emptyFields = Array.apply(null, Array(3)).map(() => '-');193 table.push([chalk.dim(groupName + '/') + chalk.bold(node.name), ...emptyFields]);194 // row for each metric195 Object.keys(node.metrics).forEach((metric: string) => {196 const metrics = node.metrics[metric as BenchmarkMetricNames];197 if (metrics) {198 const baseStats = metrics.baseStats;199 const targetStats = metrics.targetStats;200 const samplesComparison = metrics.samplesComparison;201 table.push([202 padding(1) + metric,203 `${baseStats.median.toFixed(2)}` + chalk.gray(` (± ${baseStats.medianAbsoluteDeviation.toFixed(2)}ms)`),204 `${targetStats.median.toFixed(2)}` + chalk.gray(` (± ${targetStats.medianAbsoluteDeviation.toFixed(2)}ms)`),205 chalk.bold(samplesComparison === 0 ? 'SAME' : samplesComparison === 1 ? chalk.red('SLOWER') : chalk.green('FASTER'))206 ]);207 }208 });209 }210 })211 }212 }213}214function padding(n: number) {215 return n > 0216 ? Array.apply(null, Array((n - 1) * 3))217 .map(() => ' ')218 .join('') + 'ââ '219 : '';...
analyze.ts
Source:analyze.ts
1/*2 * Copyright (c) 2019, salesforce.com, inc.3 * All rights reserved.4 * SPDX-License-Identifier: MIT5 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT6*/7import json2md from 'json2md';8import { BenchmarkComparison, ResultComparison, BenchmarkMetricNames, BenchmarkStats, ResultComparisonBenchmark, ResultComparisonGroup, ResultComparisonProject } from '@best/types';9interface MarkdownTable {10 table: {11 headers: string[];12 rows: string[][];13 }14}15interface SignificantlyChangedSummary {16 improved: string[][]17 regressed: string[][]18}19type GroupedTables = { [projectName: string]: MarkdownTable[] }20function padding(n: number) {21 return n > 022 ? Array.apply(null, Array((n - 1) * 3))23 .map(() => ' ')24 .join('') + 'ââ '25 : '';26}27function generateMarkdownFromGroupedTables(tables: GroupedTables) {28 const flattenedTables = Object.keys(tables).reduce((groups, projectName): json2md.DataObject[] => {29 groups.push({ h2: `*${projectName}*` });30 groups.push(...tables[projectName]);31 return groups;32 }, <json2md.DataObject[]>[])33 return json2md(flattenedTables);34}35function generateRow(36 name: string,37 metrics: {38 baseStats: BenchmarkStats;39 targetStats: BenchmarkStats;40 samplesComparison: 0 | 1 | -1;41 },42 includeEmojiTrend: boolean43): string[] {44 const baseStats = metrics.baseStats;45 const targetStats = metrics.targetStats;46 const samplesComparison = metrics.samplesComparison;47 const percentage = (Math.abs(baseStats.median - targetStats.median) / baseStats.median) * 100;48 const relativeTrend = targetStats.median - baseStats.median;49 const sign = Math.sign(relativeTrend) === 1 ? '+' : '';50 const comparisonEmoji = (samplesComparison === 0 ? 'ð' : samplesComparison === 1 ? 'ð' : 'ð');51 return [52 name,53 `${baseStats.median.toFixed(2)} (± ${baseStats.medianAbsoluteDeviation.toFixed(2)}ms)`,54 `${targetStats.median.toFixed(2)} (± ${targetStats.medianAbsoluteDeviation.toFixed(2)}ms)`,55 sign + relativeTrend.toFixed(1) + 'ms (' + percentage.toFixed(1) + '%)' + (includeEmojiTrend ? ` ${comparisonEmoji}` : '')56 ]57}58function generateRowsFromComparison<RowType>(stats: ResultComparison, handler: (node: ResultComparisonBenchmark, parentName: string) => RowType[], name: string = '', initialRows: RowType[] = []) {59 if (stats.type === "project" || stats.type === "group") {60 return stats.comparisons.reduce((rows, node): RowType[] => {61 if (node.type === "project" || node.type === "group") {62 return generateRowsFromComparison(node, handler, node.name, rows);63 } else if (node.type === "benchmark") {64 rows.push(...handler(node, name));65 }66 return rows;67 }, initialRows);68 } else {69 return initialRows;70 }71}72function significantlyChangedRows(stats: ResultComparison, threshold: number, name: string = '', initialRows: SignificantlyChangedSummary = { improved: [], regressed: [] }) {73 const highThreshold = Math.abs(threshold); // handle whether the threshold is positive or negative74 const lowThreshold = -1 * highThreshold;75 if (stats.type === "project" || stats.type === "group") {76 return stats.comparisons.reduce((rows, node): SignificantlyChangedSummary => {77 if (node.type === "project" || node.type === "group") {78 return significantlyChangedRows(node, threshold, node.name, rows);79 } else if (node.type === "benchmark") {80 // for the significantly changed summary, we only check for aggregate81 const metrics = node.metrics.aggregate;82 if (metrics) {83 const { baseStats, targetStats, samplesComparison } = metrics;84 85 if (samplesComparison !== 0 && baseStats.median > 1 && targetStats.median > 1) { // ensures passes Mann-Whiteney U test and results are more than 1ms86 const percentage = (Math.abs(baseStats.median - targetStats.median) / baseStats.median) * 100;87 const relativeTrend = targetStats.median - baseStats.median;88 const relativePercentage = Math.sign(relativeTrend) * percentage;89 const row = generateRow(`${name}/${node.name}`, metrics, false);90 if (relativePercentage < lowThreshold) { // less than a negative is GOOD (things got faster)91 rows.improved.push(row);92 } else if (relativePercentage > highThreshold) { // more than a positive is WORSE (things got slower)93 rows.regressed.push(row);94 }95 }96 }97 }98 return rows;99 }, initialRows)100 } else {101 return initialRows;102 }103}104function generateAllRows(stats: ResultComparison) {105 return generateRowsFromComparison(stats, (node, parentName) => {106 const rows: string[][] = [];107 const emptyFields = Array.apply(null, Array(3)).map(() => '-');108 rows.push([`${parentName}/${node.name}`, ...emptyFields]);109 Object.keys(node.metrics).forEach(metric => {110 const metrics = node.metrics[metric as BenchmarkMetricNames];111 if (metrics) {112 rows.push(generateRow(padding(1) + metric, metrics, true));113 }114 })115 return rows;116 })117}118function generateCommentWithTables(result: BenchmarkComparison, handler: (node: ResultComparisonProject | ResultComparisonGroup, baseCommit: string, targetCommit: string) => MarkdownTable[]) {119 const { baseCommit, targetCommit, comparisons } = result;120 const grouped: GroupedTables = comparisons.reduce((tables, node): GroupedTables => {121 if (node.type === "project" || node.type === "group") {122 const markdownTables = handler(node, baseCommit, targetCommit);123 if (markdownTables.length) {124 return {125 ...tables,126 [node.name]: markdownTables127 }128 }129 return tables;130 }131 return tables;132 }, <GroupedTables>{});133 return generateMarkdownFromGroupedTables(grouped);134}135export function generateComparisonSummary(result: BenchmarkComparison, threshold: number) {136 return generateCommentWithTables(result, (node, base, target) => {137 const changes = significantlyChangedRows(node, threshold);138 const tables: MarkdownTable[] = [];139 140 if (changes.improved.length) {141 tables.push({142 table: {143 headers: [`â
Improvements`, `base (\`${base}\`)`, `target (\`${target}\`)`, 'trend'],144 rows: changes.improved145 }146 })147 }148 if (changes.regressed.length) {149 tables.push({150 table: {151 headers: [`â Regressions`, `base (\`${base}\`)`, `target (\`${target}\`)`, 'trend'],152 rows: changes.regressed153 }154 });155 }156 return tables;157 })158}159function generateAllRowsTable(baseCommit: string, targetCommit: string, stats: ResultComparison): MarkdownTable {160 const { name: benchmarkName } = stats;161 const mdName = benchmarkName.replace('.benchmark', '');162 return {163 table: {164 headers: [`${mdName}`, `base (\`${baseCommit}\`)`, `target (\`${targetCommit}\`)`, 'trend'],165 rows: generateAllRows(stats)166 }167 }168}169export function generateComparisonComment(result: BenchmarkComparison) {170 const tablesMarkdown = generateCommentWithTables(result, (node, base, target) => {171 const tables = node.comparisons.map(child => {172 return generateAllRowsTable(base, target, child);173 })174 return tables;175 });176 return `# Full Results\n\n${tablesMarkdown}`;177}178// this takes all the results and recursively goes through them179// then it creates a flat list of all of the percentages of change180export function generatePercentages(stats: ResultComparison): number[] {181 return generateRowsFromComparison(stats, (node, parentName) => {182 const rows: number[] = [];183 Object.keys(node.metrics).map(metricName => {184 const metrics = node.metrics[metricName as BenchmarkMetricNames];185 if (metrics) {186 const { baseStats, targetStats, samplesComparison } = metrics;187 const baseMed = baseStats.median;188 const targetMed = targetStats.median;189 190 const percentage = Math.abs((baseMed - targetMed) / baseMed * 100);191 const relativeTrend = targetMed - baseMed;192 // ensures passes Mann-Whiteney U test and results are more than 1ms193 if (samplesComparison !== 0 && baseMed > 1 && targetMed > 1) {194 rows.push(Math.sign(relativeTrend) * percentage);195 } else {196 rows.push(0); // otherwise we count it as zero197 }198 }199 })200 return rows;201 })...
osquery_tables.js
Source:osquery_tables.js
1import { flatten, map, flatMap, sortBy } from 'lodash';2import osqueryTablesJSON from '../osquery_tables.json';3const appendPlatformKeyToTables = (parsedTables) => {4 return map(parsedTables, (platform) => {5 return platform.tables.map((table) => {6 table.platform = platform.key;7 return table;8 });9 });10};11export const normalizeTables = (tablesJSON) => {12 const { tables: parsedTables } = typeof tablesJSON === 'object' ? tablesJSON : JSON.parse(tablesJSON);13 const tablesWithPlatformKey = appendPlatformKeyToTables(parsedTables);14 const flattenedTables = flatten(tablesWithPlatformKey);15 return sortBy(flattenedTables, (table) => { return table.name; });16};17export const osqueryTables = normalizeTables(osqueryTablesJSON);18export const osqueryTableNames = flatMap(osqueryTables, (table) => {19 return table.name;...
Using AI Code Generation
1var table = require('./BestTable.js');2var t = new table();3t.setHeaders(['Name', 'ID', 'Age']);4t.addRow(['John', 1, 21]);5t.addRow(['Jim', 2, 22]);6t.addRow(['Jill', 3, 23]);7t.addRow(['Jane', 4, 24]);8t.addRow(['Jack', 5, 25]);9console.log(t.flattenedTables());10var table = require('./BestTable.js');11var t = new table();12t.setHeaders(['Name', 'ID', 'Age']);13t.addRow(['John', 1, 21]);14t.addRow(['Jim', 2, 22]);15t.addRow(['Jill', 3, 23]);16t.addRow(['Jane', 4, 24]);17t.addRow(['Jack', 5, 25]);18console.log(t.flattenedTables({separator: ',', quote: "'"}));19var table = require('./BestTable.js');20var t = new table();21t.setHeaders(['Name', 'ID', 'Age']);22t.addRow(['John', 1, 21]);23t.addRow(['Jim', 2, 22]);24t.addRow(['Jill', 3, 23]);25t.addRow(['Jane', 4, 24]);26t.addRow(['Jack', 5, 25]);27console.log(t.flattenedTables({separator: ',', quote: "'"}));28var table = require('./BestTable.js');
Using AI Code Generation
1var BestTable = require('besttable');2var table = new BestTable();3var flattenedTable = table.flattenedTables();4var row = flattenedTable.addRow();5row.addCell('test');6console.log(flattenedTable.toString());
Using AI Code Generation
1var myDoc = app.activeDocument;2var myTable = myDoc.tables[0];3var myTable2 = myTable.cells[0].tables[0];4var myTable3 = myTable.cells[1].tables[0];5var myFlattenedTables = myTable2.bestFitTable.flattenedTables;6var myTable4 = myFlattenedTables[0];7var myTable5 = myFlattenedTables[1];8var myTable6 = myFlattenedTables[2];9var myRow = myTable.rows[0];10myRow.insertCells(2);11myTable.cells[0].insertTable(myTable4);12myTable.cells[1].insertTable(myTable5);13myTable.cells[2].insertTable(myTable6);14var myFlattenedTables2 = myTable3.bestFitTable.flattenedTables;15var myTable7 = myFlattenedTables2[0];16var myTable8 = myFlattenedTables2[1];17var myTable9 = myFlattenedTables2[2];18var myRow2 = myTable.rows[1];19myRow2.insertCells(2);20myTable.cells[3].insertTable(myTable7);21myTable.cells[4].insertTable(myTable8);22myTable.cells[5].insertTable(myTable9);23myTable2.remove();24myTable3.remove();25var myDoc = app.activeDocument;26var myTable = myDoc.tables[0];27var myTable2 = myTable.cells[0].tables[0];28var myTable3 = myTable.cells[1].tables[0];29var myFlattenedTables = myTable2.bestFitTable.flattenedTables;30var myTable4 = myFlattenedTables[0];31var myTable5 = myFlattenedTables[1];32var myTable6 = myFlattenedTables[2];33var myRow = myTable.rows[0];34myRow.insertCells(2);35myTable.cells[0].insertTable(myTable4);
Using AI Code Generation
1var bestBets = require('./bestBets.js');2var query = 'knee pain';3var bb = new bestBets.BestBets(query);4bb.flattenedTables(function(err, data) {5 if (err) {6 console.log('error: ' + err);7 } else {8 console.log('best bets: ' + data);9 }10});
Using AI Code Generation
1const Bestiary = require('bestiary.js');2var bestiary = new Bestiary();3var table = bestiary.flattenedTables();4var legendaryTable = table.filter(function(monster) {5 return monster.legendaryActions;6});7var legendaryTableCR1 = legendaryTable.filter(function(monster) {8 return monster.cr == 1;9});10var legendaryTableCR1Aberration = legendaryTableCR1.filter(function(monster) {11 return monster.type == "aberration";12});13var legendaryTableCR1AberrationMedium = legendaryTableCR1Aberration.filter(function(monster) {14 return monster.size == "medium";15});
Using AI Code Generation
1importPackage(Packages.java.io);2importPackage(Packages.java.util);3importPackage(Packages.org.apache.poi.ss.usermodel);4importPackage(Packages.org.apache.poi.hssf.usermodel);5importPackage(Packages.org.apache.poi.xssf.usermodel);6importPackage(Packages.org.apache.poi.poifs.filesystem);7importPackage(Packages.org.apache.poi.hssf.util);8importPackage(Packages.org.apache.poi.ss.util);9importPackage(Packages.org.apache.poi.ss);10importPackage(Packages.org.apache.poi.ss.formula);11importPackage(Packages.org.apache.poi.ss.formula.eval);12importPackage(Packages.org.apache.poi.ss.formula.ptg);13importPackage(Packages.org.apache.poi.ss.formula.udf);14importPackage(Packages.org.apache.poi.ss.formula.functions);15importPackage(Packages.org.apache.poi.ss.formula.function);16importPackage(Packages.org.apache.poi.ss.formula.atp);17importPackage(Packages.org.apache.poi.ss.formula.constant);18importPackage(Packages.org.apache.poi.ss.formula.eval.forked);19importPackage(Packages.org.apache.poi.ss.formula.eval.forked);20importPackage(Packages.org.apache.poi.ss.formula.functions);21importPackage(Packages.org.apache.poi.ss.formula.udf);22importPackage(Packages.org.apache.poi.ss.formula.udf);23importPackage(Packages.org.apache.poi.ss.util);24importPackage(Packages.org.apache.poi.ss.util);25importPackage(Packages.org.apache.poi.ss.util);26importPackage(Packages.org.apache.p
Using AI Code Generation
1var table = new BestTable();2table.setTableId('myTable');3];4table.setData(data);5table.setTableClass('myClass');6table.setHeader(['header 1', 'header 2', 'header 3']);7table.setFooter(['footer 1', 'footer 2', 'footer 3']);8table.setTableClass('myClass');9table.setRowClass('myRowClass');10table.setCellClass('myCellClass');11table.setCellClass('myCellClass', 1, 1);12table.setCellClass('myCellClass', 2, 2);13table.setCellClass('myCellClass', 2, 2);14table.setCellClass('myCellClass', 3, 1);15table.setCellClass('myCellClass', 3, 2);16table.setCellClass('myCellClass', 3, 3);17table.setCellClass('myCellClass', 4, 1);18table.setCellClass('myCellClass', 4, 2);19table.setCellClass('myCellClass', 4, 3);20table.setCellClass('myCellClass', 5, 1);21table.setCellClass('myCellClass', 5, 2);22table.setCellClass('myCellClass', 5, 3);23table.setCellClass('myCellClass', 6, 1);24table.setCellClass('myCellClass', 6, 2);25table.setCellClass('myCellClass', 6, 3);26table.setCellClass('myCellClass', 7, 1);27table.setCellClass('myCellClass', 7, 2);28table.setCellClass('myCellClass', 7, 3);29table.setCellClass('myCellClass', 8, 1
Learn to execute automation testing from scratch with LambdaTest Learning Hub. Right from setting up the prerequisites to run your first automation test, to following best practices and diving deeper into advanced test scenarios. LambdaTest Learning Hubs compile a list of step-by-step guides to help you be proficient with different test automation frameworks i.e. Selenium, Cypress, TestNG etc.
You could also refer to video tutorials over LambdaTest YouTube channel to get step by step demonstration from industry experts.
Get 100 minutes of automation test minutes FREE!!