From ccbd26b37f6c699bff58ff0da45742567992a660 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Thu, 4 Mar 2021 18:18:16 -0700 Subject: [PATCH 01/54] [wip] instagram & core botactions/helpers - viewStories prototype - instagram navigation botactions - instagram save your login info UX botactions - elementExistsInDocument & textExistsInDocument helpers - elementExists, textExists botactions - edge case for no element found in $ botaction #102 --- apps/bot-instagram/src/main.ts | 23 +++++++++-- libs/core/src/lib/actions/abort.ts | 10 ++--- libs/core/src/lib/actions/input.ts | 17 ++++++-- libs/core/src/lib/actions/scrapers.ts | 43 +++++++++++++------- libs/core/src/lib/helpers/scrapers.ts | 26 +++++++++--- libs/instagram/src/index.ts | 2 + libs/instagram/src/lib/actions/auth.ts | 18 +++++++- libs/instagram/src/lib/actions/navigation.ts | 10 +++++ libs/instagram/src/lib/actions/stories.ts | 21 ++++++++++ libs/instagram/src/lib/selectors.ts | 7 +++- 10 files changed, 143 insertions(+), 34 deletions(-) create mode 100644 libs/instagram/src/lib/actions/navigation.ts create mode 100644 libs/instagram/src/lib/actions/stories.ts diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 24bd81cff..062a81956 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -7,9 +7,9 @@ import { givenThat, loadCookies, saveCookies, - goTo, screenshot, - logError + logError, + wait } from '@botmation/core' import { @@ -18,7 +18,9 @@ import { isLoggedIn, closeTurnOnNotificationsModal, isTurnOnNotificationsModalActive, - getInstagramBaseUrl + goToHome, + viewStories, + isSaveYourLoginInfoActive } from '@botmation/instagram' (async () => { @@ -39,7 +41,7 @@ import { loadCookies('instagram'), ), - goTo(getInstagramBaseUrl()), + goToHome, // inline, hackish but do-able if your doing something on the fly // follow the rules, don't return a value in a chain @@ -61,6 +63,16 @@ import { // in case that log in failed, lets check before we operate as a logged in user givenThat(isLoggedIn)( log('is logged in'), + + // givenThat(textExists('Save your login info?'))( + + // ), + givenThat(isSaveYourLoginInfoActive)( + log('save your login info is active') + ), + + wait(500000), + // After initial load, Instagram sometimes prompts the User with a modal... // Deal with the "Turn On Notifications" Modal, if it shows up givenThat(isTurnOnNotificationsModalActive)( @@ -68,6 +80,9 @@ import { ), screenshot('logged-in'), + + log('view stories'), + viewStories ), log('Done'), diff --git a/libs/core/src/lib/actions/abort.ts b/libs/core/src/lib/actions/abort.ts index 9f82b835a..05ca8ad41 100644 --- a/libs/core/src/lib/actions/abort.ts +++ b/libs/core/src/lib/actions/abort.ts @@ -50,13 +50,13 @@ export const restart = (...actions: BotAction[]): BotAction => // manually resolving actions in a Pipe instead of using pipe()() to control the AbortLineSignal processing actionResult = await action(page, ...injects, pipeObject) - // unique recycle aborting behavior + // unique restart aborting behavior if (isAbortLineSignal(actionResult)) { - // cannot recycle an infinity abort, if desirable, create another BotAction - maybe with a customizing HO function - // assembledLines 1 => recycle actions - // assembledLines 0 or 2+ => abort recycle + // cannot restart an infinity abort, if desirable, create another BotAction - maybe with a customizing HO function + // assembledLines 1 => restart actions + // assembledLines 0 or 2+ => abort restart if (actionResult.assembledLines > 1 || actionResult.assembledLines === 0) { - return processAbortLineSignal(actionResult, 2) // abort the line and abort recycle() + return processAbortLineSignal(actionResult, 2) // abort the line and abort restart() } else { restartActions = true; pipeObject = wrapValueInPipe(actionResult.pipeValue) diff --git a/libs/core/src/lib/actions/input.ts b/libs/core/src/lib/actions/input.ts index a5c9b6577..c3ecc4d34 100644 --- a/libs/core/src/lib/actions/input.ts +++ b/libs/core/src/lib/actions/input.ts @@ -2,15 +2,24 @@ import { BotAction } from '../interfaces/bot-actions' /** * @description Manually left-click an HTML element on the page given the provided HTML selector - * @param selector + * @param selector */ export const click = (selector: string): BotAction => async(page) => - await page.click(selector) + page.click(selector) + + +/** + * Click the element in the DOM with the provided text + * @param text + */ +export const clickText = (text: string): BotAction => async(page) => { + +} /** * @description Types the `copy` provided with a "keyboard" * Can be used to fill text based form fields - * @param copy + * @param copy */ export const type = (copy: string): BotAction => async(page) => - await page.keyboard.type(copy) \ No newline at end of file + page.keyboard.type(copy) diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index 1298c9002..e42fb5a4a 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -5,32 +5,47 @@ import { EvaluateFn } from 'puppeteer' import * as cheerio from 'cheerio' -import { BotAction, ScraperBotAction } from '../interfaces/bot-actions' +import { BotAction, ConditionalBotAction, ScraperBotAction } from '../interfaces/bot-actions' import { inject } from '../actions/inject' import { errors } from '../actions/errors' -import { getElementOuterHTML, getElementsOuterHTML } from '../helpers/scrapers' +import { elementExistsInDocument, getElementOuterHTML, getElementsOuterHTML, textExistsInDocument } from '../helpers/scrapers' import { unpipeInjects } from '../helpers/pipe' import { pipe } from './assembly-lines' +import { map } from './pipe' /** * @description Inject htmlParser for ScraperBotAction's - * - * @param htmlParserFunction + * + * @param htmlParserFunction */ export const htmlParser = (htmlParserFunction: Function) => - (...actions: BotAction[]): BotAction => + (...actions: BotAction[]): BotAction => pipe()( inject(htmlParserFunction)( errors('htmlParser()()')(...actions) ) ) +/** + * A ConditionalBotAction to return a Boolean value + * True if element is found, false if element is not found + * @param elementSelector + */ +export const elementExists = (elementSelector: string): ConditionalBotAction => + evaluate(elementExistsInDocument, elementSelector) + +/** + * + * @param text + */ +export const textExists = (text: string): ConditionalBotAction => evaluate(textExistsInDocument, text) + /** * Returns the first Element that matches the provided HTML Selector - * @param htmlSelector + * @param htmlSelector */ -export const $ = (htmlSelector: string, higherOrderHTMLParser?: Function): ScraperBotAction => +export const $ = (htmlSelector: string, higherOrderHTMLParser?: Function): ScraperBotAction => async(page, ...injects) => { let parser: Function @@ -48,14 +63,14 @@ export const $ = (htmlSelector: string, higherOrderHTMLParser } const scrapedHTML = await page.evaluate(getElementOuterHTML, htmlSelector) - return parser(scrapedHTML) + return scrapedHTML ? parser(scrapedHTML) : undefined } - + /** * Returns an array of parsed HTML Element's as objects (dependent on the html parser used) that match the provided HTML Selector - * @param htmlSelector + * @param htmlSelector */ -export const $$ = (htmlSelector: string, higherOrderHTMLParser?: Function): ScraperBotAction => +export const $$ = (htmlSelector: string, higherOrderHTMLParser?: Function): ScraperBotAction => async(page, ...injects) => { let parser: Function @@ -74,7 +89,7 @@ export const $$ = (htmlSelector: string, higherOrderHTMLPar const scrapedHTMLs = await page.evaluate(getElementsOuterHTML, htmlSelector) - const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => parser(scrapedHTML)) + const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => parser(scrapedHTML)) return cheerioEls as any as R } @@ -82,7 +97,7 @@ export const $$ = (htmlSelector: string, higherOrderHTMLPar /** * Evaluate functions inside the `page` context * @param functionToEvaluate - * @param functionParams + * @param functionParams */ -export const evaluate = (functionToEvaluate: EvaluateFn, ...functionParams: any[]): BotAction => +export const evaluate = (functionToEvaluate: EvaluateFn, ...functionParams: any[]): BotAction => async(page) => await page.evaluate(functionToEvaluate, ...functionParams) diff --git a/libs/core/src/lib/helpers/scrapers.ts b/libs/core/src/lib/helpers/scrapers.ts index c8af9b995..c6dad5e85 100644 --- a/libs/core/src/lib/helpers/scrapers.ts +++ b/libs/core/src/lib/helpers/scrapers.ts @@ -1,15 +1,31 @@ /** * Returns an escaped string representation of the HTML for the given html selector. Includes children elements. - * @param htmlSelector + * @param htmlSelector */ /* istanbul ignore next */ -export const getElementOuterHTML = (htmlSelector: string): string|undefined => +export const getElementOuterHTML = (htmlSelector: string): string|undefined => document.querySelector(htmlSelector)?.outerHTML /** * Returns an array of escaped string representations of HTML code matching the given html selector. Includes children elements. - * @param htmlSelector + * @param htmlSelector */ /* istanbul ignore next */ -export const getElementsOuterHTML = (htmlSelector: string): string[] => - Array.from(document.querySelectorAll(htmlSelector)).map(el => el.outerHTML) \ No newline at end of file +export const getElementsOuterHTML = (htmlSelector: string): string[] => + Array.from(document.querySelectorAll(htmlSelector)).map(el => el.outerHTML) + +/** + * + * @param htmlSelector + */ +export const elementExistsInDocument = (htmlSelector: string): boolean => + document.querySelector(htmlSelector) !== null + +/** + * Check the DOM for text. If found, return TRUE, otherwise return FALSE + * @param text + */ +export const textExistsInDocument = (text: string): boolean => + ( document.documentElement.textContent || document.documentElement.innerText ) + .indexOf(text) > -1 + diff --git a/libs/instagram/src/index.ts b/libs/instagram/src/index.ts index b5212bab2..081713e27 100644 --- a/libs/instagram/src/index.ts +++ b/libs/instagram/src/index.ts @@ -1,5 +1,7 @@ export * from './lib/actions/auth' export * from './lib/actions/modals' +export * from './lib/actions/navigation' +export * from './lib/actions/stories' export * from './lib/constants/modals' export * from './lib/constants/urls' diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index d23c52d72..ec63bc482 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -1,4 +1,4 @@ -import { ConditionalBotAction } from '@botmation/core' +import { ConditionalBotAction, textExists } from '@botmation/core' import { BotAction } from '@botmation/core' import { chain } from '@botmation/core' @@ -58,3 +58,19 @@ export const login = ({username, password}: {username: string, password: string} log('Login Complete') ) ) + +/** + * During initial login, in a brand new environment, Instagram might prompt the User to save their login information + * This will disrupt feed access, therefore this function can be used to check if that is being presented + */ +export const isSaveYourLoginInfoActive: ConditionalBotAction = textExists('Save Your Login Info?') + +/** + * + */ +export const clickSaveYourLoginInfoYesButton: BotAction = click('main button:first-child') + +/** + * + */ +export const clickSaveYourLoginInfoNoButton: BotAction = click('main button:last-child') diff --git a/libs/instagram/src/lib/actions/navigation.ts b/libs/instagram/src/lib/actions/navigation.ts new file mode 100644 index 000000000..7eacfe0fa --- /dev/null +++ b/libs/instagram/src/lib/actions/navigation.ts @@ -0,0 +1,10 @@ +import { BotAction, goTo } from "@botmation/core" + + +export const goToHome: BotAction = goTo('https://www.instagram.com/') + +export const goToMessaging: BotAction = goTo('https://www.instagram.com/direct/inbox/') + +export const goToExplore: BotAction = goTo('https://www.instagram.com/explore/') + +export const goToSettings: BotAction = goTo('https://www.instagram.com/accounts/edit/') diff --git a/libs/instagram/src/lib/actions/stories.ts b/libs/instagram/src/lib/actions/stories.ts new file mode 100644 index 000000000..a610be83f --- /dev/null +++ b/libs/instagram/src/lib/actions/stories.ts @@ -0,0 +1,21 @@ +/** + * BotActions related to the main feed Stories feature + */ + +import { BotAction, click, forAsLong, wait, elementExists, givenThat } from "@botmation/core"; +import { FIRST_STORY, STORIES_VIEWER_NEXT_STORY_ICON } from "../selectors"; + +/** + * + * @param page + * @param injects + */ +export const viewStories: BotAction = + givenThat(elementExists(FIRST_STORY + ' button'))( + click(FIRST_STORY + ' button'), + forAsLong(elementExists(STORIES_VIEWER_NEXT_STORY_ICON))( + click(STORIES_VIEWER_NEXT_STORY_ICON), + wait(500) + ) + ) + diff --git a/libs/instagram/src/lib/selectors.ts b/libs/instagram/src/lib/selectors.ts index fee4ca212..e13577e41 100644 --- a/libs/instagram/src/lib/selectors.ts +++ b/libs/instagram/src/lib/selectors.ts @@ -11,4 +11,9 @@ export const FORM_AUTH_SUBMIT_BUTTON_SELECTOR = 'html body section main form but // // Modals export const MAIN_MODAL_SELECTOR = 'html body div[role="dialog"]' -export const MAIN_MODAL_HEADER_SELECTOR = 'html body div[role="dialog"] h2' \ No newline at end of file +export const MAIN_MODAL_HEADER_SELECTOR = 'html body div[role="dialog"] h2' + +// +// Feed: Stories +export const FIRST_STORY = 'body main ul li[tabindex="-1"]' +export const STORIES_VIEWER_NEXT_STORY_ICON = 'body div section div div section div button div.coreSpriteRightChevron' From 4a6e03eb84907ea18904fef1dfe4bf10fb258084 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Thu, 4 Mar 2021 20:22:45 -0700 Subject: [PATCH 02/54] clickText - viewStories wip --- apps/bot-instagram/src/main.ts | 26 +++++++++----------------- libs/core/src/index.ts | 1 + libs/core/src/lib/actions/input.ts | 7 ++++--- libs/instagram/src/lib/actions/auth.ts | 15 ++++++++++++--- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 062a81956..5cc70205e 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -9,7 +9,8 @@ import { saveCookies, screenshot, logError, - wait + wait, + waitForNavigation } from '@botmation/core' import { @@ -20,7 +21,8 @@ import { isTurnOnNotificationsModalActive, goToHome, viewStories, - isSaveYourLoginInfoActive + isSaveYourLoginInfoActive, + clickSaveYourLoginInfoNoButton } from '@botmation/instagram' (async () => { @@ -35,7 +37,7 @@ import { log('Botmation running'), // Sets up the injects for BotFileAction's (optional) - files({cookies_directory: 'simple'})( + files()( // Takes the name of the file to load cookies from // Match this value with the same used in saveCookies() loadCookies('instagram'), @@ -50,30 +52,20 @@ import { // }, // lets log in, if we are a guest - log('checking Guest status'), givenThat(isGuest) ( - log('is guest so logging in'), login({username: 'account', password: 'password'}), // <- put your username and password here - files({cookies_directory: 'simple'})( + files()( saveCookies('instagram'), // the Bot will skip login, on next run, by loading cookies - ), - log('Saved Cookies') + ) ), // in case that log in failed, lets check before we operate as a logged in user givenThat(isLoggedIn)( - log('is logged in'), - - // givenThat(textExists('Save your login info?'))( - - // ), givenThat(isSaveYourLoginInfoActive)( - log('save your login info is active') + clickSaveYourLoginInfoNoButton, + waitForNavigation ), - wait(500000), - - // After initial load, Instagram sometimes prompts the User with a modal... // Deal with the "Turn On Notifications" Modal, if it shows up givenThat(isTurnOnNotificationsModalActive)( closeTurnOnNotificationsModal diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 1b519b284..640cee079 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -36,6 +36,7 @@ export * from './lib/helpers/cases' export * from './lib/helpers/console' export * from './lib/helpers/files' export * from './lib/helpers/indexed-db' +export * from './lib/helpers/input' export * from './lib/helpers/local-storage' export * from './lib/helpers/navigation' export * from './lib/helpers/pipe' diff --git a/libs/core/src/lib/actions/input.ts b/libs/core/src/lib/actions/input.ts index c3ecc4d34..ebefcf0d9 100644 --- a/libs/core/src/lib/actions/input.ts +++ b/libs/core/src/lib/actions/input.ts @@ -1,4 +1,6 @@ +import { clickElementWithText } from '../helpers/input' import { BotAction } from '../interfaces/bot-actions' +import { evaluate } from './scrapers' /** * @description Manually left-click an HTML element on the page given the provided HTML selector @@ -12,9 +14,8 @@ export const click = (selector: string): BotAction => async(page) => * Click the element in the DOM with the provided text * @param text */ -export const clickText = (text: string): BotAction => async(page) => { - -} +export const clickText = (text: string): BotAction => + evaluate(clickElementWithText, text) /** * @description Types the `copy` provided with a "keyboard" diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index ec63bc482..40d47850e 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -1,4 +1,4 @@ -import { ConditionalBotAction, textExists } from '@botmation/core' +import { clickText, ConditionalBotAction, textExists } from '@botmation/core' import { BotAction } from '@botmation/core' import { chain } from '@botmation/core' @@ -59,6 +59,15 @@ export const login = ({username, password}: {username: string, password: string} ) ) +/** + * + * @param page + */ +export const logout: BotAction = async(page) => { + // todo try deleting something from local storage/indexeddb instead of trying to click links + // todo after delete data, navigate/refresh home page +} + /** * During initial login, in a brand new environment, Instagram might prompt the User to save their login information * This will disrupt feed access, therefore this function can be used to check if that is being presented @@ -68,9 +77,9 @@ export const isSaveYourLoginInfoActive: ConditionalBotAction = textExists('Save /** * */ -export const clickSaveYourLoginInfoYesButton: BotAction = click('main button:first-child') +export const clickSaveYourLoginInfoYesButton: BotAction = clickText('Save Info') /** * */ -export const clickSaveYourLoginInfoNoButton: BotAction = click('main button:last-child') +export const clickSaveYourLoginInfoNoButton: BotAction = clickText('Not Now') From b3b287b48e1587ce385047b22717eedd63d2dc25 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Thu, 4 Mar 2021 20:23:19 -0700 Subject: [PATCH 03/54] clickElementWithText helper --- libs/core/src/lib/helpers/input.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 libs/core/src/lib/helpers/input.ts diff --git a/libs/core/src/lib/helpers/input.ts b/libs/core/src/lib/helpers/input.ts new file mode 100644 index 000000000..ef49c4d9d --- /dev/null +++ b/libs/core/src/lib/helpers/input.ts @@ -0,0 +1,13 @@ + +/** + * + * @param text + */ +export const clickElementWithText = (text: string) => { + const xpath = `//*[text()='${text}']` + const matchingElement = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; + + if (matchingElement instanceof HTMLElement) { + matchingElement.click() + } +} From 93ed6f805e70fa75944fbd71e41cced0bbf6d618 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 16:18:07 -0700 Subject: [PATCH 04/54] working viewStories - needed wait 1s after clicking 1st story in top menu --- apps/bot-instagram/src/main.ts | 8 ++++---- libs/instagram/src/lib/actions/stories.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 5cc70205e..72f533166 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -9,8 +9,7 @@ import { saveCookies, screenshot, logError, - wait, - waitForNavigation + waitForNavigation, } from '@botmation/core' import { @@ -53,7 +52,7 @@ import { // lets log in, if we are a guest givenThat(isGuest) ( - login({username: 'account', password: 'password'}), // <- put your username and password here + login({username: 'lagmahol', password: 'i4MFr33!'}), // <- put your username and password here files()( saveCookies('instagram'), // the Bot will skip login, on next run, by loading cookies ) @@ -73,7 +72,8 @@ import { screenshot('logged-in'), - log('view stories'), + log('view all stories'), + viewStories ), diff --git a/libs/instagram/src/lib/actions/stories.ts b/libs/instagram/src/lib/actions/stories.ts index a610be83f..a29898bf0 100644 --- a/libs/instagram/src/lib/actions/stories.ts +++ b/libs/instagram/src/lib/actions/stories.ts @@ -2,7 +2,7 @@ * BotActions related to the main feed Stories feature */ -import { BotAction, click, forAsLong, wait, elementExists, givenThat } from "@botmation/core"; +import { BotAction, click, forAsLong, wait, elementExists, givenThat, log } from "@botmation/core"; import { FIRST_STORY, STORIES_VIEWER_NEXT_STORY_ICON } from "../selectors"; /** @@ -13,6 +13,7 @@ import { FIRST_STORY, STORIES_VIEWER_NEXT_STORY_ICON } from "../selectors"; export const viewStories: BotAction = givenThat(elementExists(FIRST_STORY + ' button'))( click(FIRST_STORY + ' button'), + wait(1000), forAsLong(elementExists(STORIES_VIEWER_NEXT_STORY_ICON))( click(STORIES_VIEWER_NEXT_STORY_ICON), wait(500) From 7ddacfaaf6dcf6cee329aa8f8cefbb08e3909c22 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 16:23:37 -0700 Subject: [PATCH 05/54] clean up --- apps/bot-instagram/src/main.ts | 2 +- libs/core/src/lib/actions/scrapers.ts | 7 ++++--- libs/instagram/src/lib/actions/stories.ts | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 72f533166..c3bb2ee7d 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -52,7 +52,7 @@ import { // lets log in, if we are a guest givenThat(isGuest) ( - login({username: 'lagmahol', password: 'i4MFr33!'}), // <- put your username and password here + login({username: 'account', password: 'password'}), // <- put your username and password here files()( saveCookies('instagram'), // the Bot will skip login, on next run, by loading cookies ) diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index e42fb5a4a..6e87af4b7 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -39,7 +39,8 @@ export const elementExists = (elementSelector: string): ConditionalBotAction => * * @param text */ -export const textExists = (text: string): ConditionalBotAction => evaluate(textExistsInDocument, text) +export const textExists = (text: string): ConditionalBotAction => + evaluate(textExistsInDocument, text) /** * Returns the first Element that matches the provided HTML Selector @@ -95,9 +96,9 @@ export const $$ = (htmlSelector: string, higherOrderHTMLPar } /** - * Evaluate functions inside the `page` context + * Evaluate functions inside the `page` context. Run Javascript functions inside Puppeteer Pages as if copying/pasting the code in the Console then running it * @param functionToEvaluate * @param functionParams */ export const evaluate = (functionToEvaluate: EvaluateFn, ...functionParams: any[]): BotAction => - async(page) => await page.evaluate(functionToEvaluate, ...functionParams) + async(page) => page.evaluate(functionToEvaluate, ...functionParams) diff --git a/libs/instagram/src/lib/actions/stories.ts b/libs/instagram/src/lib/actions/stories.ts index a29898bf0..d2f930f07 100644 --- a/libs/instagram/src/lib/actions/stories.ts +++ b/libs/instagram/src/lib/actions/stories.ts @@ -2,11 +2,11 @@ * BotActions related to the main feed Stories feature */ -import { BotAction, click, forAsLong, wait, elementExists, givenThat, log } from "@botmation/core"; +import { BotAction, click, forAsLong, wait, elementExists, givenThat } from "@botmation/core"; import { FIRST_STORY, STORIES_VIEWER_NEXT_STORY_ICON } from "../selectors"; /** - * + * When on the Home page, run this BotAction to cause the bot to open the Stories Theater Mode with the 1st Story then view them all until there are no more left * @param page * @param injects */ From 6aa6b9010df9776dbdd75552f2d300b4ae9b0026 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 16:26:44 -0700 Subject: [PATCH 06/54] fix #102 $$ multiple elements scraping - undefined edge case will not attempt to parse undefine but return undefined --- libs/core/src/lib/actions/scrapers.ts | 3 +-- libs/core/src/lib/helpers/scrapers.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index 6e87af4b7..8bf419578 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -89,8 +89,7 @@ export const $$ = (htmlSelector: string, higherOrderHTMLPar } const scrapedHTMLs = await page.evaluate(getElementsOuterHTML, htmlSelector) - - const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => parser(scrapedHTML)) + const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => scrapedHTML ? parser(scrapedHTML) : undefined) return cheerioEls as any as R } diff --git a/libs/core/src/lib/helpers/scrapers.ts b/libs/core/src/lib/helpers/scrapers.ts index c6dad5e85..a5e55eb1e 100644 --- a/libs/core/src/lib/helpers/scrapers.ts +++ b/libs/core/src/lib/helpers/scrapers.ts @@ -12,7 +12,7 @@ export const getElementOuterHTML = (htmlSelector: string): string|undefined => */ /* istanbul ignore next */ export const getElementsOuterHTML = (htmlSelector: string): string[] => - Array.from(document.querySelectorAll(htmlSelector)).map(el => el.outerHTML) + Array.from(document.querySelectorAll(htmlSelector)).map(el => el?.outerHTML) /** * From c287de951c75461ff36bb6051e1a5c74d3e81ffe Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 16:32:44 -0700 Subject: [PATCH 07/54] unnecessary returns empty array anyway --- libs/core/src/lib/actions/scrapers.ts | 2 +- libs/core/src/lib/helpers/scrapers.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index 8bf419578..5e4a144f3 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -89,7 +89,7 @@ export const $$ = (htmlSelector: string, higherOrderHTMLPar } const scrapedHTMLs = await page.evaluate(getElementsOuterHTML, htmlSelector) - const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => scrapedHTML ? parser(scrapedHTML) : undefined) + const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => parser(scrapedHTML)) return cheerioEls as any as R } diff --git a/libs/core/src/lib/helpers/scrapers.ts b/libs/core/src/lib/helpers/scrapers.ts index a5e55eb1e..c6dad5e85 100644 --- a/libs/core/src/lib/helpers/scrapers.ts +++ b/libs/core/src/lib/helpers/scrapers.ts @@ -12,7 +12,7 @@ export const getElementOuterHTML = (htmlSelector: string): string|undefined => */ /* istanbul ignore next */ export const getElementsOuterHTML = (htmlSelector: string): string[] => - Array.from(document.querySelectorAll(htmlSelector)).map(el => el?.outerHTML) + Array.from(document.querySelectorAll(htmlSelector)).map(el => el.outerHTML) /** * From 0e8787e809d7fd528efeeaa23a8942492a382953 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 17:19:57 -0700 Subject: [PATCH 08/54] botactions getCookies & deleteCookies & instagram logout - used in tandem in a pipe to delete cookies for urls associated with a browser page - this in replace of deleteAllCookiesForCurrentUrl BotAction - instagram logout but for a clean logout, need to clear indexeddb too --- apps/bot-instagram/src/main.ts | 12 +++++-- libs/core/src/lib/actions/cookies.ts | 45 +++++++++++++++++++++----- libs/instagram/src/lib/actions/auth.ts | 21 ++++++------ 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index c3bb2ee7d..2daab1abf 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -10,6 +10,7 @@ import { screenshot, logError, waitForNavigation, + wait, } from '@botmation/core' import { @@ -21,7 +22,8 @@ import { goToHome, viewStories, isSaveYourLoginInfoActive, - clickSaveYourLoginInfoNoButton + clickSaveYourLoginInfoNoButton, + logout } from '@botmation/instagram' (async () => { @@ -72,9 +74,13 @@ import { screenshot('logged-in'), - log('view all stories'), + // viewStories, - viewStories + wait(5000), + + logout, + + wait(5000) ), log('Done'), diff --git a/libs/core/src/lib/actions/cookies.ts b/libs/core/src/lib/actions/cookies.ts index 874c66e1f..115e37e08 100644 --- a/libs/core/src/lib/actions/cookies.ts +++ b/libs/core/src/lib/actions/cookies.ts @@ -1,26 +1,29 @@ import { promises as fs } from 'fs' -import { BotFilesAction } from '../interfaces/bot-actions' +import { BotAction, BotFilesAction } from '../interfaces/bot-actions' import { enrichBotFileOptionsWithDefaults } from '../helpers/files' import { BotFileOptions } from '../interfaces' import { getFileUrl } from '../helpers/files' -import { unpipeInjects } from '../helpers/pipe' +import { getInjectsPipeValue, injectsHavePipe, unpipeInjects } from '../helpers/pipe' +import { pipe } from './assembly-lines' +import { Protocol } from 'puppeteer' +import { forAll } from './loops' /** * @description Parse page's cookies and save them as JSON in a local file * Relies on BotFileOptions (options), to determine URL * Works with files()() for setting cookies directory & parent directory * botFileOptions overrides injected values from files()() - * @param fileName + * @param fileName * @example saveCookies('cookies') -> creates `cookies.json` */ -export const saveCookies = (fileName: string, botFileOptions?: Partial): BotFilesAction => +export const saveCookies = (fileName: string, botFileOptions?: Partial): BotFilesAction => async(page, ...injects) => { const [,injectedOptions] = unpipeInjects(injects, 1) // botFileOptions is higher order param that overwrites injected options const hydratedOptions = enrichBotFileOptionsWithDefaults({...injectedOptions, ...botFileOptions}) - + const cookies = await page.cookies() await fs.writeFile(getFileUrl(hydratedOptions.cookies_directory, hydratedOptions, fileName) + '.json', JSON.stringify(cookies, null, 2)) } @@ -30,10 +33,10 @@ export const saveCookies = (fileName: string, botFileOptions?: Partial): BotFilesAction => +export const loadCookies = (fileName: string, botFileOptions?: Partial): BotFilesAction => async(page, ...injects) => { const [,injectedOptions] = unpipeInjects(injects, 1) @@ -46,4 +49,30 @@ export const loadCookies = (fileName: string, botFileOptions?: Partial => async(page) => + page.cookies(...urls) + + +/** + * Delete cookies provided. Can be provided through HO param OR through pipe value as a fallback + * @param cookies + */ +export const deleteCookies = (...cookies: Protocol.Network.Cookie[]): BotAction => async(page, ...injects) => { + if (cookies.length === 0) { + if (injectsHavePipe(injects)) { + const pipeValue = getInjectsPipeValue(injects) + if (Array.isArray(pipeValue) && pipeValue.length > 0) { + cookies = pipeValue + } + } + } + + return page.deleteCookie(...cookies) +} diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index 40d47850e..4d11a7aab 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -1,10 +1,11 @@ -import { clickText, ConditionalBotAction, textExists } from '@botmation/core' +import { clickText, ConditionalBotAction, pipe, textExists } from '@botmation/core' import { BotAction } from '@botmation/core' -import { chain } from '@botmation/core' -import { goTo, waitForNavigation, wait } from '@botmation/core' +import { chain, errors } from '@botmation/core' +import { goTo, reload, waitForNavigation, wait } from '@botmation/core' +import { getCookies, deleteCookies } from '@botmation/core' import { click, type } from '@botmation/core' -import { getIndexedDBValue, indexedDBStore } from '@botmation/core' +import { getIndexedDBValue, indexedDBStore, clearAllLocalStorage } from '@botmation/core' import { map } from '@botmation/core' import { log } from '@botmation/core' @@ -14,7 +15,6 @@ import { FORM_AUTH_PASSWORD_INPUT_SELECTOR, FORM_AUTH_SUBMIT_BUTTON_SELECTOR } from '../selectors' -import { errors } from '@botmation/core' /** * @description ConditionalBotAction that resolves TRUE if the User is NOT logged in @@ -63,10 +63,13 @@ export const login = ({username, password}: {username: string, password: string} * * @param page */ -export const logout: BotAction = async(page) => { - // todo try deleting something from local storage/indexeddb instead of trying to click links - // todo after delete data, navigate/refresh home page -} +export const logout: BotAction = pipe()( + getCookies(), + deleteCookies(), + clearAllLocalStorage, + // todo clear all indexeddb + reload() +) /** * During initial login, in a brand new environment, Instagram might prompt the User to save their login information From 29b2ac38b5409948536868b142b3d55a686de016 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 18:07:22 -0700 Subject: [PATCH 09/54] instagram logout concept - wip deleteIndexedDB by database name --- apps/bot-instagram/src/main.ts | 9 ++++- libs/core/src/lib/actions/indexed-db.ts | 14 +++++++ libs/core/src/lib/helpers/indexed-db.ts | 51 +++++++++++++++++-------- libs/instagram/src/lib/actions/auth.ts | 10 +++-- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 2daab1abf..f13302030 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -62,9 +62,11 @@ import { // in case that log in failed, lets check before we operate as a logged in user givenThat(isLoggedIn)( + givenThat(isSaveYourLoginInfoActive)( clickSaveYourLoginInfoNoButton, - waitForNavigation + waitForNavigation, + log('save ur login info closed') ), // Deal with the "Turn On Notifications" Modal, if it shows up @@ -75,12 +77,15 @@ import { screenshot('logged-in'), // viewStories, + log('screenshot taken'), wait(5000), logout, - wait(5000) + log('logout complete'), + + wait(15000) ), log('Done'), diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 45951954c..01fb48548 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -7,6 +7,20 @@ import { PipeValue } from '../types/pipe-value' import { isObjectWithKey, isObjectWithValue } from '../types/objects' import { getQueryKey, getQueryKeyValue } from '../types/database' import { pipe } from './assembly-lines' +import { deleteIndexedDBDatabase } from '../helpers/indexed-db' + +/** + * + * @param page + */ +export const deleteIndexedDB = (databaseName?: string): BotIndexedDBAction => async(page, ...injects) => { + const [, , injectDatabaseName] = unpipeInjects(injects, 3) + + await page.evaluate( + deleteIndexedDBDatabase, + databaseName ?? injectDatabaseName + ) +} /** * @description It's a higher-order BotAction that sets injects for identifying information of one IndexedDB store diff --git a/libs/core/src/lib/helpers/indexed-db.ts b/libs/core/src/lib/helpers/indexed-db.ts index 5aeeaf90f..37acd8cbe 100644 --- a/libs/core/src/lib/helpers/indexed-db.ts +++ b/libs/core/src/lib/helpers/indexed-db.ts @@ -2,26 +2,45 @@ // The following functions are evaluated in a Puppeteer Page instance's browser context // +/** + * + * @param databaseName + */ +export function deleteIndexedDBDatabase(databaseName: string) { + return new Promise((resolve, reject) => { + const DBDeleteRequest = window.indexedDB.deleteDatabase(databaseName); + + DBDeleteRequest.onerror = function(event) { + event.stopPropagation() + return reject(this.error) + }; + + DBDeleteRequest.onsuccess = function() { + return resolve() + }; + }) +} + /** * @description Async function to set an IndexedDB Store value by key - * @param databaseName - * @param databaseVersion - * @param storeName - * @param key - * @param value + * @param databaseName + * @param databaseVersion + * @param storeName + * @param key + * @param value */ export function setIndexedDBStoreValue(databaseName: string, databaseVersion: number, storeName: string, key: string, value: any) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const openRequest = indexedDB.open(databaseName, databaseVersion) openRequest.onerror = function(this: IDBRequest, ev: Event) { ev.stopPropagation() - return reject(this.error) + return reject(this.error) } // when adding a new store, do a higher db version number to invoke this function: - openRequest.onupgradeneeded = function(this: IDBOpenDBRequest, ev: IDBVersionChangeEvent): any { + openRequest.onupgradeneeded = function(this: IDBOpenDBRequest, ev: IDBVersionChangeEvent): any { /* istanbul ignore next */ if (!this.result.objectStoreNames.contains(storeName)) { this.result.createObjectStore(storeName) @@ -42,24 +61,24 @@ export function setIndexedDBStoreValue(databaseName: string, databaseVersion: nu } catch (error) { db.close() return reject(error) - } + } } }) } /** * @description Async function to get an IndexedDB Store value by key - * @param databaseName - * @param databaseVersion - * @param storeName - * @param key + * @param databaseName + * @param databaseVersion + * @param storeName + * @param key */ export function getIndexedDBStoreValue(databaseName: string, databaseVersion: number, storeName: string, key: string) { return new Promise((resolve, reject) => { const openRequest = indexedDB.open(databaseName, databaseVersion) openRequest.onerror = function(this: IDBRequest, ev: Event) { - return reject(this.error) + return reject(this.error) } openRequest.onsuccess = function(this: IDBRequest, ev: Event) { @@ -69,13 +88,13 @@ export function getIndexedDBStoreValue(databaseName: string, databaseVersion: nu const tx = db.transaction(storeName, 'readonly') .objectStore(storeName) .get(key) - + tx.onsuccess = function(this: IDBRequest) { const result = this.result // If key isn't found, the result resolved is undefined db.close() return resolve(result) } - + } catch (error) { db.close() return reject(error) diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index 4d11a7aab..842b425b1 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -5,7 +5,7 @@ import { chain, errors } from '@botmation/core' import { goTo, reload, waitForNavigation, wait } from '@botmation/core' import { getCookies, deleteCookies } from '@botmation/core' import { click, type } from '@botmation/core' -import { getIndexedDBValue, indexedDBStore, clearAllLocalStorage } from '@botmation/core' +import { getIndexedDBValue, indexedDBStore, deleteIndexedDB, clearAllLocalStorage } from '@botmation/core' import { map } from '@botmation/core' import { log } from '@botmation/core' @@ -66,9 +66,13 @@ export const login = ({username, password}: {username: string, password: string} export const logout: BotAction = pipe()( getCookies(), deleteCookies(), + log('cookies delete'), clearAllLocalStorage, - // todo clear all indexeddb - reload() + log('clear all local storage'), + deleteIndexedDB('redux'), // not working + log('delete redux indexeddb'), + reload(), + log('reload') ) /** From 9382026bcf35c1401045cd79f706f6fe905a1715 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 5 Mar 2021 21:23:11 -0700 Subject: [PATCH 10/54] wip delete IndexedDB database by name --- libs/core/src/lib/actions/indexed-db.ts | 4 ++++ libs/core/src/lib/helpers/indexed-db.ts | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 01fb48548..b42413efd 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -16,10 +16,14 @@ import { deleteIndexedDBDatabase } from '../helpers/indexed-db' export const deleteIndexedDB = (databaseName?: string): BotIndexedDBAction => async(page, ...injects) => { const [, , injectDatabaseName] = unpipeInjects(injects, 3) + console.log('evaluate') + await page.evaluate( deleteIndexedDBDatabase, databaseName ?? injectDatabaseName ) + + console.log('evaluate complete') } /** diff --git a/libs/core/src/lib/helpers/indexed-db.ts b/libs/core/src/lib/helpers/indexed-db.ts index 37acd8cbe..bcb3ab303 100644 --- a/libs/core/src/lib/helpers/indexed-db.ts +++ b/libs/core/src/lib/helpers/indexed-db.ts @@ -2,20 +2,29 @@ // The following functions are evaluated in a Puppeteer Page instance's browser context // +import { logError, logMessage } from "./console"; + /** * * @param databaseName */ export function deleteIndexedDBDatabase(databaseName: string) { return new Promise((resolve, reject) => { - const DBDeleteRequest = window.indexedDB.deleteDatabase(databaseName); + const DBDeleteRequest = indexedDB.deleteDatabase(databaseName); DBDeleteRequest.onerror = function(event) { + logError('delete IndexedDB Database name = ' + databaseName) event.stopPropagation() return reject(this.error) }; DBDeleteRequest.onsuccess = function() { + console.log('?') + return resolve() + }; + + DBDeleteRequest.onblocked = function(e) { + logMessage('blocked attempt to delete IndexedDB Database name = ' + databaseName) return resolve() }; }) From 6bdc61931b0d96674c82328025eeb9390a878b67 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Sat, 6 Mar 2021 21:17:30 -0700 Subject: [PATCH 11/54] instagram logout --- libs/core/src/lib/actions/indexed-db.ts | 7 ++----- libs/core/src/lib/helpers/indexed-db.ts | 4 ++-- libs/instagram/src/lib/actions/auth.ts | 7 +------ 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index b42413efd..2ab136fae 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -14,16 +14,13 @@ import { deleteIndexedDBDatabase } from '../helpers/indexed-db' * @param page */ export const deleteIndexedDB = (databaseName?: string): BotIndexedDBAction => async(page, ...injects) => { - const [, , injectDatabaseName] = unpipeInjects(injects, 3) - - console.log('evaluate') + const [, , injectDatabaseName] = unpipeInjects(injects, 3) + // todo e2e test that does not block attempt to delete await page.evaluate( deleteIndexedDBDatabase, databaseName ?? injectDatabaseName ) - - console.log('evaluate complete') } /** diff --git a/libs/core/src/lib/helpers/indexed-db.ts b/libs/core/src/lib/helpers/indexed-db.ts index bcb3ab303..252b7091b 100644 --- a/libs/core/src/lib/helpers/indexed-db.ts +++ b/libs/core/src/lib/helpers/indexed-db.ts @@ -19,11 +19,11 @@ export function deleteIndexedDBDatabase(databaseName: string) { }; DBDeleteRequest.onsuccess = function() { - console.log('?') return resolve() }; - DBDeleteRequest.onblocked = function(e) { + DBDeleteRequest.onblocked = function(event) { + event.stopPropagation() logMessage('blocked attempt to delete IndexedDB Database name = ' + databaseName) return resolve() }; diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index 842b425b1..235622dba 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -66,13 +66,8 @@ export const login = ({username, password}: {username: string, password: string} export const logout: BotAction = pipe()( getCookies(), deleteCookies(), - log('cookies delete'), clearAllLocalStorage, - log('clear all local storage'), - deleteIndexedDB('redux'), // not working - log('delete redux indexeddb'), - reload(), - log('reload') + reload() // instagram will delete IndexedDB 'redux' db after reload ) /** From fe3fbe917c55b326b114e9bc14fef77a4358bcd5 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 8 Mar 2021 17:34:02 -0700 Subject: [PATCH 12/54] e2e test $() for not finding node - edge-case behavior modified -> originally threw error, now returns undefined --- libs/core/src/lib/actions/scrapers.spec.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/core/src/lib/actions/scrapers.spec.ts b/libs/core/src/lib/actions/scrapers.spec.ts index 31a5a437d..11573f18e 100644 --- a/libs/core/src/lib/actions/scrapers.spec.ts +++ b/libs/core/src/lib/actions/scrapers.spec.ts @@ -69,7 +69,18 @@ describe('[Botmation] actions/scraping', () => { expect(mockPage.evaluate).toHaveBeenNthCalledWith(1, expect.any(Function), 'test-1') expect(mockPage.evaluate).toHaveBeenNthCalledWith(2, expect.any(Function), 'test-1-1') expect(mockPage.evaluate).toHaveBeenNthCalledWith(3, expect.any(Function), '5') - expect(result).toEqual(10) + expect(result).toBeUndefined() + }) + + it('$() should return undefined if it doesnt find the HTML node based on the selector', async() => { + page = await browser.newPage() + + await page.goto(BASE_URL); + + const noResult = await $('does-not-exist')(page) + + expect(noResult).toBeUndefined + await page.close() }) it('$$() should call Page.evaluate() with a helper function and the correct html selector', async() => { From c24b6dd52bb2551347cae829c755f99fa8365d22 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 17:32:09 -0700 Subject: [PATCH 13/54] e2e test textExists & elementExists --- libs/core/src/lib/actions/scrapers.spec.ts | 62 ++++++++++++++++------ 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/libs/core/src/lib/actions/scrapers.spec.ts b/libs/core/src/lib/actions/scrapers.spec.ts index 11573f18e..3360b5b8e 100644 --- a/libs/core/src/lib/actions/scrapers.spec.ts +++ b/libs/core/src/lib/actions/scrapers.spec.ts @@ -2,7 +2,7 @@ import { Page, Browser } from 'puppeteer' const puppeteer = require('puppeteer') import { BASE_URL } from './../mocks' -import { $, $$, htmlParser, evaluate } from './scrapers' +import { $, $$, htmlParser, evaluate, elementExists, textExists } from './scrapers' // Mock inject()() jest.mock('../actions/inject', () => { @@ -63,24 +63,11 @@ describe('[Botmation] actions/scraping', () => { await $('test-1')(mockPage, (d:any) => d) // mock html parser await $('test-1-1', (d:any) => d)(mockPage) - - const result = await $('5', (d: any) => 10)(mockPage, (d: any) => d) + await $('5', (d: any) => 10)(mockPage, (d: any) => d) expect(mockPage.evaluate).toHaveBeenNthCalledWith(1, expect.any(Function), 'test-1') expect(mockPage.evaluate).toHaveBeenNthCalledWith(2, expect.any(Function), 'test-1-1') expect(mockPage.evaluate).toHaveBeenNthCalledWith(3, expect.any(Function), '5') - expect(result).toBeUndefined() - }) - - it('$() should return undefined if it doesnt find the HTML node based on the selector', async() => { - page = await browser.newPage() - - await page.goto(BASE_URL); - - const noResult = await $('does-not-exist')(page) - - expect(noResult).toBeUndefined - await page.close() }) it('$$() should call Page.evaluate() with a helper function and the correct html selector', async() => { @@ -118,7 +105,7 @@ describe('[Botmation] actions/scraping', () => { }) // - // Unit-Tests + // e2e it('Should scrape joke (2 paragraph elements) and 1 home link (anchor element) and grab their text', async() => { // setup test page = await browser.newPage() @@ -138,6 +125,49 @@ describe('[Botmation] actions/scraping', () => { await page.close() }) + it('$() should return undefined if it doesnt find the HTML node based on the selector', async() => { + page = await browser.newPage() + + await page.goto(BASE_URL); + + const noResult = await $('does-not-exist')(page) + + expect(noResult).toBeUndefined + await page.close() + }) + + it('elementExists should return TRUE if the selector is found in the DOM otherwise return FALSE', async() => { + page = await browser.newPage() + + await page.goto(BASE_URL) + + const result = await elementExists('form input[name="answer"]')(page) + const noResult = await elementExists('not-an-html-element')(page) + + expect(result).toEqual(true) + expect(noResult).toEqual(false) + + await page.close() + }) + + it('textExists should return TRUE if the text is found in the DOM otherwise return FALSE', async() => { + page = await browser.newPage() + + await page.goto(BASE_URL) + + const result = await textExists('Do you want to hear a joke?')(page) + const partialResult = await textExists(' to hear a ')(page) + + const noResult = await textExists('The local neighborhood Spiderman is on vacation')(page) + + expect(result).toEqual(true) + expect(partialResult).toEqual(true) + + expect(noResult).toEqual(false) + + await page.close() + }) + // clean up afterAll(async() => { jest.unmock('../actions/inject') From 51dfea692123e60026ccd21b356d32080b2bf348 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 17:36:38 -0700 Subject: [PATCH 14/54] remove code-coverage check in evaluated func's - these two are covered by the e2e tests added in the last commit --- libs/core/src/lib/helpers/scrapers.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/core/src/lib/helpers/scrapers.ts b/libs/core/src/lib/helpers/scrapers.ts index c6dad5e85..605637867 100644 --- a/libs/core/src/lib/helpers/scrapers.ts +++ b/libs/core/src/lib/helpers/scrapers.ts @@ -18,6 +18,7 @@ export const getElementsOuterHTML = (htmlSelector: string): string[] => * * @param htmlSelector */ +/* istanbul ignore next */ export const elementExistsInDocument = (htmlSelector: string): boolean => document.querySelector(htmlSelector) !== null @@ -25,6 +26,7 @@ export const elementExistsInDocument = (htmlSelector: string): boolean => * Check the DOM for text. If found, return TRUE, otherwise return FALSE * @param text */ +/* istanbul ignore next */ export const textExistsInDocument = (text: string): boolean => ( document.documentElement.textContent || document.documentElement.innerText ) .indexOf(text) > -1 From 12ee8d7802b72a9be0f5d1bfc985563653026be1 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 17:42:59 -0700 Subject: [PATCH 15/54] e2e clickText - ignore serialized code injected into puppeteer, covered by e2e --- libs/core/src/lib/actions/input.spec.ts | 12 +++++++++++- libs/core/src/lib/helpers/input.ts | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/libs/core/src/lib/actions/input.spec.ts b/libs/core/src/lib/actions/input.spec.ts index 8fafc7dba..55b6681de 100644 --- a/libs/core/src/lib/actions/input.spec.ts +++ b/libs/core/src/lib/actions/input.spec.ts @@ -1,6 +1,6 @@ import { Page, Browser } from 'puppeteer' -import { click, type } from './input' +import { click, clickText, type } from './input' import { FORM_SUBMIT_BUTTON_SELECTOR, FORM_TEXT_INPUT_SELECTOR, BASE_URL } from './../mocks' @@ -69,4 +69,14 @@ describe('[Botmation] actions/input', () => { await page.close() }) + it('clickText should click the element with the text provided', async() => { + page = await browser.newPage() + await page.goto(BASE_URL) + + await clickText('Example Link 2')(page) + await page.waitForNavigation() + + expect(page.url()).toEqual('http://localhost:8080/example2.html') + }) + }) diff --git a/libs/core/src/lib/helpers/input.ts b/libs/core/src/lib/helpers/input.ts index ef49c4d9d..eb59608dc 100644 --- a/libs/core/src/lib/helpers/input.ts +++ b/libs/core/src/lib/helpers/input.ts @@ -3,6 +3,7 @@ * * @param text */ +/* istanbul ignore next */ export const clickElementWithText = (text: string) => { const xpath = `//*[text()='${text}']` const matchingElement = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; From 5b11056b8fbb52028fca9514d7817057d65ac784 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 17:44:34 -0700 Subject: [PATCH 16/54] e2e clickText edge-case --- libs/core/src/lib/actions/input.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/core/src/lib/actions/input.spec.ts b/libs/core/src/lib/actions/input.spec.ts index 55b6681de..a4563ae98 100644 --- a/libs/core/src/lib/actions/input.spec.ts +++ b/libs/core/src/lib/actions/input.spec.ts @@ -75,6 +75,7 @@ describe('[Botmation] actions/input', () => { await clickText('Example Link 2')(page) await page.waitForNavigation() + await clickText('dlkfjnsldkfjslkdfjbslkfdjbsdfg')(page) // can attempt to click soemthing that doesn't exist expect(page.url()).toEqual('http://localhost:8080/example2.html') }) From 42a03cd039c65bbad4a0cc6004e4cc47a3856f86 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 17:52:52 -0700 Subject: [PATCH 17/54] integration test getCookies --- libs/core/src/lib/actions/cookies.spec.ts | 36 +++++++++++++++++++---- libs/core/src/lib/actions/cookies.ts | 2 -- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/libs/core/src/lib/actions/cookies.spec.ts b/libs/core/src/lib/actions/cookies.spec.ts index f9d9c5b61..85cd4cfbd 100644 --- a/libs/core/src/lib/actions/cookies.spec.ts +++ b/libs/core/src/lib/actions/cookies.spec.ts @@ -2,7 +2,7 @@ import { Page } from 'puppeteer' import { promises as fs, Stats } from 'fs' import { getFileUrl } from './../helpers/files' -import { saveCookies, loadCookies } from './cookies' +import { saveCookies, loadCookies, getCookies } from './cookies' import { BotFileOptions } from './../interfaces/bot-file-options' import { wrapValueInPipe } from './../helpers/pipe' @@ -35,10 +35,14 @@ describe('[Botmation] actions/cookies', () => { } ] - let mockPage: Page = { - cookies: jest.fn(() => COOKIES_JSON), - setCookie: jest.fn() - } as any as Page + let mockPage: Page + + beforeEach(() => { + mockPage = { + cookies: jest.fn(() => COOKIES_JSON), + setCookie: jest.fn(), + } as any as Page + }) // // saveCookies() Unit/Integration Test @@ -67,6 +71,28 @@ describe('[Botmation] actions/cookies', () => { }) }) + // + // getCookies + it('getCookies() should call Puppeteer page cookies() method with urls provided, with safe default of no urls provided', async() => { + await getCookies()(mockPage) + + expect(mockPage.cookies).toHaveBeenCalledTimes(1) + + await getCookies('url 1')(mockPage) + + expect(mockPage.cookies).toHaveBeenNthCalledWith(2, 'url 1') + + await getCookies('url 5', 'url 25', 'url 54')(mockPage) + + expect(mockPage.cookies).toHaveBeenNthCalledWith(3, 'url 5', 'url 25', 'url 54') + }) + + // + // deleteCookies + it('deleteCookies() should call puppeteer page deleteCookies() method with cookies provided through HO param or fallback pipe value if value is an array', async() => { + + }) + // // Clean up afterAll(async() => { diff --git a/libs/core/src/lib/actions/cookies.ts b/libs/core/src/lib/actions/cookies.ts index 115e37e08..ed39d81ec 100644 --- a/libs/core/src/lib/actions/cookies.ts +++ b/libs/core/src/lib/actions/cookies.ts @@ -5,9 +5,7 @@ import { enrichBotFileOptionsWithDefaults } from '../helpers/files' import { BotFileOptions } from '../interfaces' import { getFileUrl } from '../helpers/files' import { getInjectsPipeValue, injectsHavePipe, unpipeInjects } from '../helpers/pipe' -import { pipe } from './assembly-lines' import { Protocol } from 'puppeteer' -import { forAll } from './loops' /** * @description Parse page's cookies and save them as JSON in a local file From 0c8a6e3237a3a4bc42d3a98e65e8df531b1e3775 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 20:58:40 -0700 Subject: [PATCH 18/54] integration test deleteCookies --- libs/core/src/lib/actions/cookies.spec.ts | 34 +++++++++++++++++++++-- libs/core/src/lib/actions/cookies.ts | 5 +++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/libs/core/src/lib/actions/cookies.spec.ts b/libs/core/src/lib/actions/cookies.spec.ts index 85cd4cfbd..17f0c190f 100644 --- a/libs/core/src/lib/actions/cookies.spec.ts +++ b/libs/core/src/lib/actions/cookies.spec.ts @@ -1,8 +1,8 @@ -import { Page } from 'puppeteer' +import { Page, Protocol } from 'puppeteer' import { promises as fs, Stats } from 'fs' import { getFileUrl } from './../helpers/files' -import { saveCookies, loadCookies, getCookies } from './cookies' +import { saveCookies, loadCookies, getCookies, deleteCookies } from './cookies' import { BotFileOptions } from './../interfaces/bot-file-options' import { wrapValueInPipe } from './../helpers/pipe' @@ -41,6 +41,7 @@ describe('[Botmation] actions/cookies', () => { mockPage = { cookies: jest.fn(() => COOKIES_JSON), setCookie: jest.fn(), + deleteCookie: jest.fn() } as any as Page }) @@ -90,7 +91,36 @@ describe('[Botmation] actions/cookies', () => { // // deleteCookies it('deleteCookies() should call puppeteer page deleteCookies() method with cookies provided through HO param or fallback pipe value if value is an array', async() => { + const cookies = ['a', 'b', 'c', 'd'] as any as Protocol.Network.Cookie[] + const pipeCookies = ['5', '2', '3', '1'] as any as Protocol.Network.Cookie[] + await deleteCookies(...cookies)(mockPage) + + expect(mockPage.deleteCookie).toHaveBeenCalledWith('a', 'b', 'c', 'd') + expect(mockPage.deleteCookie).toHaveBeenCalledTimes(1) + + await deleteCookies()(mockPage, wrapValueInPipe(pipeCookies)) + + expect(mockPage.deleteCookie).toHaveBeenNthCalledWith(2, '5', '2', '3', '1') + expect(mockPage.deleteCookie).toHaveBeenCalledTimes(2) + + const higherOrderOverwritesPipeValue = ['blue', 'green', 'red'] as any as Protocol.Network.Cookie[] + + await deleteCookies(...higherOrderOverwritesPipeValue)(mockPage, wrapValueInPipe(pipeCookies)) + + expect(mockPage.deleteCookie).toHaveBeenNthCalledWith(3, 'blue', 'green', 'red') + expect(mockPage.deleteCookie).toHaveBeenCalledTimes(3) + + // no cookies specified to delete so no delete call + const notCookies = {} + + await deleteCookies()(mockPage, wrapValueInPipe(notCookies)) + + expect(mockPage.deleteCookie).toHaveBeenCalledTimes(3) + + await deleteCookies()(mockPage) // no HO, no injects (pipe value) + + expect(mockPage.deleteCookie).toHaveBeenCalledTimes(3) }) // diff --git a/libs/core/src/lib/actions/cookies.ts b/libs/core/src/lib/actions/cookies.ts index ed39d81ec..b79cfb552 100644 --- a/libs/core/src/lib/actions/cookies.ts +++ b/libs/core/src/lib/actions/cookies.ts @@ -6,6 +6,7 @@ import { BotFileOptions } from '../interfaces' import { getFileUrl } from '../helpers/files' import { getInjectsPipeValue, injectsHavePipe, unpipeInjects } from '../helpers/pipe' import { Protocol } from 'puppeteer' +import { logWarning } from '../helpers/console' /** * @description Parse page's cookies and save them as JSON in a local file @@ -72,5 +73,7 @@ export const deleteCookies = (...cookies: Protocol.Network.Cookie[]): BotAction } } - return page.deleteCookie(...cookies) + if (cookies.length > 0) { + return page.deleteCookie(...cookies) + } } From 64e8bb6c456066bafbe45af043b5eaa5437329d3 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 21:27:48 -0700 Subject: [PATCH 19/54] e2e cookies testing --- assets/server/cookies.html | 35 +++++++++++++++++++++++ libs/core/src/lib/actions/cookies.spec.ts | 34 ++++++++++++++++++++++ libs/core/src/lib/mocks/urls.ts | 3 +- 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 assets/server/cookies.html diff --git a/assets/server/cookies.html b/assets/server/cookies.html new file mode 100644 index 000000000..6a8d8c79c --- /dev/null +++ b/assets/server/cookies.html @@ -0,0 +1,35 @@ + + + + Testing: Cookies + + + + +

Cookies Page

+ +
+
+ + +
+
+ + diff --git a/libs/core/src/lib/actions/cookies.spec.ts b/libs/core/src/lib/actions/cookies.spec.ts index 17f0c190f..c38b317d8 100644 --- a/libs/core/src/lib/actions/cookies.spec.ts +++ b/libs/core/src/lib/actions/cookies.spec.ts @@ -1,4 +1,6 @@ import { Page, Protocol } from 'puppeteer' +import * as puppeteer from 'puppeteer' + import { promises as fs, Stats } from 'fs' import { getFileUrl } from './../helpers/files' @@ -6,6 +8,8 @@ import { saveCookies, loadCookies, getCookies, deleteCookies } from './cookies' import { BotFileOptions } from './../interfaces/bot-file-options' import { wrapValueInPipe } from './../helpers/pipe' +import { COOKIES_URL } from '../mocks' +import { pipe } from './assembly-lines' /** * @description Cookies BotAction's @@ -123,6 +127,36 @@ describe('[Botmation] actions/cookies', () => { expect(mockPage.deleteCookie).toHaveBeenCalledTimes(3) }) + // e2e + it('can deleteCookies() based on piped value from getCookies()', async() => { + const browser = await puppeteer.launch() + const page = await browser.newPage() + + await page.goto(COOKIES_URL) + const initialCookies = await page.cookies() + + expect(initialCookies.length).toEqual(2) + + await pipe()( + getCookies(), + // todo tap like BotAction? It would be similar to Map except it ignores whatever the cb returns and simply returns the pipe value + async(page, pipeObject) => { + expect(pipeObject.value.length).toEqual(2) + expect(pipeObject.value[0]).toEqual({"domain": "localhost", "expires": -1, "httpOnly": false, "name": "sessionId", "path": "/", "sameParty": false, "secure": false, "session": true, "size": 16, "sourcePort": 8080, "sourceScheme": "NonSecure", "value": "1235711"}) + expect(pipeObject.value[1]).toEqual({"domain": "localhost", "expires": -1, "httpOnly": false, "name": "username", "path": "/", "sameParty": false, "secure": false, "session": true, "size": 16, "sourcePort": 8080, "sourceScheme": "NonSecure", "value": "John Doe"}) + + return pipeObject.value + }, + deleteCookies() + )(page) + + const finalCookies = await page.cookies() + expect(finalCookies.length).toEqual(0) + + await page.close() + await browser.close() + }) + // // Clean up afterAll(async() => { diff --git a/libs/core/src/lib/mocks/urls.ts b/libs/core/src/lib/mocks/urls.ts index a6a41e684..80c567a9f 100644 --- a/libs/core/src/lib/mocks/urls.ts +++ b/libs/core/src/lib/mocks/urls.ts @@ -2,4 +2,5 @@ export const BASE_URL = 'http://localhost:8080' export const EXAMPLE_URL = 'http://localhost:8080/example.html' export const EXAMPLE_URL2 = 'http://localhost:8080/example2.html' -export const SUCCESS_URL = 'http://localhost:8080/success.html' \ No newline at end of file +export const SUCCESS_URL = 'http://localhost:8080/success.html' +export const COOKIES_URL = 'http://localhost:8080/cookies.html' From 542d40472a7753eee7c87527a40f90038410cc8c Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 9 Mar 2021 21:29:05 -0700 Subject: [PATCH 20/54] comment simplification --- libs/core/src/lib/actions/cookies.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/core/src/lib/actions/cookies.spec.ts b/libs/core/src/lib/actions/cookies.spec.ts index c38b317d8..9e46e1f03 100644 --- a/libs/core/src/lib/actions/cookies.spec.ts +++ b/libs/core/src/lib/actions/cookies.spec.ts @@ -139,7 +139,7 @@ describe('[Botmation] actions/cookies', () => { await pipe()( getCookies(), - // todo tap like BotAction? It would be similar to Map except it ignores whatever the cb returns and simply returns the pipe value + // todo tap() BotAction? similar to map() BotAction but ignore cb return / auto return pipe value async(page, pipeObject) => { expect(pipeObject.value.length).toEqual(2) expect(pipeObject.value[0]).toEqual({"domain": "localhost", "expires": -1, "httpOnly": false, "name": "sessionId", "path": "/", "sameParty": false, "secure": false, "session": true, "size": 16, "sourcePort": 8080, "sourceScheme": "NonSecure", "value": "1235711"}) From df0b481e8404c4f8d3f6f395b61ce12f74863665 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 15 Mar 2021 17:40:54 -0600 Subject: [PATCH 21/54] [concept] runOnDiceRoll()() BotAction #85 - run assembled botactions if a virtual dice with X sides rolls a 1 - number of sides X is set via HO param, default is 1 - number to roll can be set via 2nd HO param, default is 1 - rollDice() helper function --- libs/core/src/lib/actions/branching.ts | 19 ++++++++++++++++++- libs/core/src/lib/helpers/branching.ts | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 libs/core/src/lib/helpers/branching.ts diff --git a/libs/core/src/lib/actions/branching.ts b/libs/core/src/lib/actions/branching.ts index 59a128a95..45bab4d57 100644 --- a/libs/core/src/lib/actions/branching.ts +++ b/libs/core/src/lib/actions/branching.ts @@ -6,10 +6,11 @@ import { ConditionalBotAction, BotAction } from '../interfaces/bot-actions' import { pipeInjects } from '../helpers/pipe' -import { pipe } from './assembly-lines' +import { assemblyLine, pipe } from './assembly-lines' import { PipeValue } from '../types/pipe-value' import { AbortLineSignal, isAbortLineSignal } from '../types/abort-line-signal' import { processAbortLineSignal } from '../helpers/abort' +import { rollDice } from '../helpers/branching' /** * @description Higher Order BotAction that accepts a ConditionalBotAction (pipeable, that returns a boolean) and based on what boolean it resolves, @@ -39,3 +40,19 @@ export const givenThat = } } } + +/** + * @future once sync BotActions are supported, update this with givenThat(diceRoll(val))(...actions) + * @param numberOfDiceSides + * @param numberToRoll + */ +export const runOnDiceRoll = + (numberOfDiceSides = 1, numberToRoll = 1) => + (...actions: BotAction[]): BotAction => + async(page, ...injects) => { + const diceRoll = rollDice(numberOfDiceSides) + + if (diceRoll === numberToRoll) { + return assemblyLine()(...actions)(page, ...injects) + } + } diff --git a/libs/core/src/lib/helpers/branching.ts b/libs/core/src/lib/helpers/branching.ts new file mode 100644 index 000000000..9ab8b502a --- /dev/null +++ b/libs/core/src/lib/helpers/branching.ts @@ -0,0 +1,7 @@ +/** + * Roll a dice with the given number of sides + * @param numberOfDiceSides + * @return the value the dice landed on + */ +export const rollDice = (numberOfDiceSides = 1): number => + Math.floor(Math.random() * numberOfDiceSides) + 1 From 8e9998823aceec69ee1897a0d3106c6322931bfb Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 15 Mar 2021 17:47:26 -0600 Subject: [PATCH 22/54] naming --- libs/core/src/lib/actions/branching.ts | 6 ++---- libs/core/src/lib/helpers/branching.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/core/src/lib/actions/branching.ts b/libs/core/src/lib/actions/branching.ts index 45bab4d57..0d89c8843 100644 --- a/libs/core/src/lib/actions/branching.ts +++ b/libs/core/src/lib/actions/branching.ts @@ -10,7 +10,7 @@ import { assemblyLine, pipe } from './assembly-lines' import { PipeValue } from '../types/pipe-value' import { AbortLineSignal, isAbortLineSignal } from '../types/abort-line-signal' import { processAbortLineSignal } from '../helpers/abort' -import { rollDice } from '../helpers/branching' +import { diceRoll } from '../helpers/branching' /** * @description Higher Order BotAction that accepts a ConditionalBotAction (pipeable, that returns a boolean) and based on what boolean it resolves, @@ -50,9 +50,7 @@ export const runOnDiceRoll = (numberOfDiceSides = 1, numberToRoll = 1) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { - const diceRoll = rollDice(numberOfDiceSides) - - if (diceRoll === numberToRoll) { + if (diceRoll(numberOfDiceSides) === numberToRoll) { return assemblyLine()(...actions)(page, ...injects) } } diff --git a/libs/core/src/lib/helpers/branching.ts b/libs/core/src/lib/helpers/branching.ts index 9ab8b502a..35e95bd9b 100644 --- a/libs/core/src/lib/helpers/branching.ts +++ b/libs/core/src/lib/helpers/branching.ts @@ -3,5 +3,5 @@ * @param numberOfDiceSides * @return the value the dice landed on */ -export const rollDice = (numberOfDiceSides = 1): number => +export const diceRoll = (numberOfDiceSides = 1): number => Math.floor(Math.random() * numberOfDiceSides) + 1 From b284515ce28e1d393c795daa1b10d8195ec44aa2 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 19:45:54 -0600 Subject: [PATCH 23/54] onDiceRoll name change & helper diceRoll() unit-test - added edge-case for dice with zero or negative number of sides which on roll, returns 0 --- libs/core/src/lib/actions/branching.ts | 2 +- libs/core/src/lib/helpers/branching.spec.ts | 37 +++++++++++++++++++++ libs/core/src/lib/helpers/branching.ts | 9 +++-- 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 libs/core/src/lib/helpers/branching.spec.ts diff --git a/libs/core/src/lib/actions/branching.ts b/libs/core/src/lib/actions/branching.ts index 0d89c8843..451d450e2 100644 --- a/libs/core/src/lib/actions/branching.ts +++ b/libs/core/src/lib/actions/branching.ts @@ -46,7 +46,7 @@ export const givenThat = * @param numberOfDiceSides * @param numberToRoll */ -export const runOnDiceRoll = +export const onDiceRoll = (numberOfDiceSides = 1, numberToRoll = 1) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { diff --git a/libs/core/src/lib/helpers/branching.spec.ts b/libs/core/src/lib/helpers/branching.spec.ts new file mode 100644 index 000000000..9284e6281 --- /dev/null +++ b/libs/core/src/lib/helpers/branching.spec.ts @@ -0,0 +1,37 @@ +import { diceRoll } from './branching' + +/** + * @description Branching Helpers + */ +describe('[Botmation] helpers/branching', () => { + + // + // Unit Test + it('diceRoll() returns a random number from 1 to n where n is the numberOfDiceSides', () => { + const noParams = diceRoll() + const diceWithTwoSides = diceRoll(2) + const diceWithNineSides = diceRoll(9) + const diceWithTwentyFiveSides = diceRoll(25) + + expect(noParams).toEqual(1) + + expect(diceWithTwoSides).toBeGreaterThanOrEqual(1) + expect(diceWithTwoSides).toBeLessThanOrEqual(2) + + expect(diceWithNineSides).toBeGreaterThanOrEqual(1) + expect(diceWithNineSides).toBeLessThanOrEqual(9) + + expect(diceWithTwentyFiveSides).toBeGreaterThanOrEqual(1) + expect(diceWithTwentyFiveSides).toBeLessThanOrEqual(25) + + // unique edge cases + const diceWithZeroSides = diceRoll(0) + const diceWithNegativeOneSide = diceRoll(-1) + const diceWithNegativeSides = diceRoll(-10) + + expect(diceWithZeroSides).toEqual(0) + expect(diceWithNegativeOneSide).toEqual(0) + expect(diceWithNegativeSides).toEqual(0) + }) + +}) diff --git a/libs/core/src/lib/helpers/branching.ts b/libs/core/src/lib/helpers/branching.ts index 35e95bd9b..c14717657 100644 --- a/libs/core/src/lib/helpers/branching.ts +++ b/libs/core/src/lib/helpers/branching.ts @@ -1,7 +1,10 @@ /** * Roll a dice with the given number of sides - * @param numberOfDiceSides - * @return the value the dice landed on + * @param numberOfDiceSides n where n >= 1 + * if n is 0 or negative, then return value is 0 + * @return 1 -> n (including n) */ export const diceRoll = (numberOfDiceSides = 1): number => - Math.floor(Math.random() * numberOfDiceSides) + 1 + numberOfDiceSides >= 1 ? + Math.floor(Math.random() * numberOfDiceSides) + 1 : + 0 From 5371c7efb51186d11d190f089261c78b1e6cb3e5 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:05:47 -0600 Subject: [PATCH 24/54] probably() botaction concept - new BotAction group: Random - rollDice() has probability limitation of no probabilities greater than 50% therefore need another botaction - consolidate rollDice()() and probably()() ? --- libs/core/src/lib/actions/branching.ts | 16 +------ libs/core/src/lib/actions/random.spec.ts | 27 ++++++++++++ libs/core/src/lib/actions/random.ts | 37 +++++++++++++++++ libs/core/src/lib/helpers/branching.spec.ts | 37 ----------------- libs/core/src/lib/helpers/branching.ts | 10 ----- libs/core/src/lib/helpers/random.spec.ts | 46 +++++++++++++++++++++ libs/core/src/lib/helpers/random.ts | 16 +++++++ 7 files changed, 127 insertions(+), 62 deletions(-) create mode 100644 libs/core/src/lib/actions/random.spec.ts create mode 100644 libs/core/src/lib/actions/random.ts delete mode 100644 libs/core/src/lib/helpers/branching.spec.ts delete mode 100644 libs/core/src/lib/helpers/branching.ts create mode 100644 libs/core/src/lib/helpers/random.spec.ts create mode 100644 libs/core/src/lib/helpers/random.ts diff --git a/libs/core/src/lib/actions/branching.ts b/libs/core/src/lib/actions/branching.ts index 451d450e2..591bd4278 100644 --- a/libs/core/src/lib/actions/branching.ts +++ b/libs/core/src/lib/actions/branching.ts @@ -6,11 +6,10 @@ import { ConditionalBotAction, BotAction } from '../interfaces/bot-actions' import { pipeInjects } from '../helpers/pipe' -import { assemblyLine, pipe } from './assembly-lines' +import { pipe } from './assembly-lines' import { PipeValue } from '../types/pipe-value' import { AbortLineSignal, isAbortLineSignal } from '../types/abort-line-signal' import { processAbortLineSignal } from '../helpers/abort' -import { diceRoll } from '../helpers/branching' /** * @description Higher Order BotAction that accepts a ConditionalBotAction (pipeable, that returns a boolean) and based on what boolean it resolves, @@ -41,16 +40,3 @@ export const givenThat = } } -/** - * @future once sync BotActions are supported, update this with givenThat(diceRoll(val))(...actions) - * @param numberOfDiceSides - * @param numberToRoll - */ -export const onDiceRoll = - (numberOfDiceSides = 1, numberToRoll = 1) => - (...actions: BotAction[]): BotAction => - async(page, ...injects) => { - if (diceRoll(numberOfDiceSides) === numberToRoll) { - return assemblyLine()(...actions)(page, ...injects) - } - } diff --git a/libs/core/src/lib/actions/random.spec.ts b/libs/core/src/lib/actions/random.spec.ts new file mode 100644 index 000000000..77b8ac29a --- /dev/null +++ b/libs/core/src/lib/actions/random.spec.ts @@ -0,0 +1,27 @@ +import { Page } from 'puppeteer' + +/** + * @description Random BotActions + */ +describe('[Botmation] actions/random', () => { + + let mockPage: Page + + beforeEach(() => { + mockPage = { + click: jest.fn(), + keyboard: { + type: jest.fn() + }, + url: jest.fn(() => ''), + goto: jest.fn() + } as any as Page + }) + + // + // diceRoll() Unit Tests + it('diceRoll() should roll dice and if the correct number to roll appears, than the assembled actions are ran, otherwise the assembled actions are skipped', async() => { + + }) + +}) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts new file mode 100644 index 000000000..7a47ee82c --- /dev/null +++ b/libs/core/src/lib/actions/random.ts @@ -0,0 +1,37 @@ +import { BotAction } from '../interfaces/bot-actions' +import { assemblyLine } from './assembly-lines' +import { calculateDiceRoll, randomDecimal } from '../helpers/random' + +/** + * Run assembled BotActions ONLY if a virtually rolled dice lands on the number to roll + * This works for probabilitilities of 50% or less. For higher probabilities, use the probably()() BotAction + * @future once sync BotActions are supported, update with givenThat()() using a function wrapper to convert diceRoll() into a boolean expression (dice roll correct number or not) + * @param numberOfDiceSides + * @param numberToRoll + */ +export const rollDice = + (numberOfDiceSides = 1, numberToRoll = 1) => + (...actions: BotAction[]): BotAction => + async(page, ...injects) => { + if (calculateDiceRoll(numberOfDiceSides) === numberToRoll) { + return assemblyLine()(...actions)(page, ...injects) + } + } + +// todo problem about this design -> it limits the range of "random" to 50% or smaller +// if dev wants to run BotActions with a 60% chance or higher, this won't work + +/** + * Run assembled BotActions based on a probability + * if random number generated is within the probability range, then assembled BotActions are ran + * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing + * @param probability number 0-1 ie .6 = 60% + */ +export const probably = + (probability = 1) => + (...actions: BotAction[]): BotAction => + async(page, ...injects) => { + if (randomDecimal() <= probability) { + return assemblyLine()(...actions)(page, ...injects) + } + } diff --git a/libs/core/src/lib/helpers/branching.spec.ts b/libs/core/src/lib/helpers/branching.spec.ts deleted file mode 100644 index 9284e6281..000000000 --- a/libs/core/src/lib/helpers/branching.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { diceRoll } from './branching' - -/** - * @description Branching Helpers - */ -describe('[Botmation] helpers/branching', () => { - - // - // Unit Test - it('diceRoll() returns a random number from 1 to n where n is the numberOfDiceSides', () => { - const noParams = diceRoll() - const diceWithTwoSides = diceRoll(2) - const diceWithNineSides = diceRoll(9) - const diceWithTwentyFiveSides = diceRoll(25) - - expect(noParams).toEqual(1) - - expect(diceWithTwoSides).toBeGreaterThanOrEqual(1) - expect(diceWithTwoSides).toBeLessThanOrEqual(2) - - expect(diceWithNineSides).toBeGreaterThanOrEqual(1) - expect(diceWithNineSides).toBeLessThanOrEqual(9) - - expect(diceWithTwentyFiveSides).toBeGreaterThanOrEqual(1) - expect(diceWithTwentyFiveSides).toBeLessThanOrEqual(25) - - // unique edge cases - const diceWithZeroSides = diceRoll(0) - const diceWithNegativeOneSide = diceRoll(-1) - const diceWithNegativeSides = diceRoll(-10) - - expect(diceWithZeroSides).toEqual(0) - expect(diceWithNegativeOneSide).toEqual(0) - expect(diceWithNegativeSides).toEqual(0) - }) - -}) diff --git a/libs/core/src/lib/helpers/branching.ts b/libs/core/src/lib/helpers/branching.ts deleted file mode 100644 index c14717657..000000000 --- a/libs/core/src/lib/helpers/branching.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Roll a dice with the given number of sides - * @param numberOfDiceSides n where n >= 1 - * if n is 0 or negative, then return value is 0 - * @return 1 -> n (including n) - */ -export const diceRoll = (numberOfDiceSides = 1): number => - numberOfDiceSides >= 1 ? - Math.floor(Math.random() * numberOfDiceSides) + 1 : - 0 diff --git a/libs/core/src/lib/helpers/random.spec.ts b/libs/core/src/lib/helpers/random.spec.ts new file mode 100644 index 000000000..f15a62dc6 --- /dev/null +++ b/libs/core/src/lib/helpers/random.spec.ts @@ -0,0 +1,46 @@ +import { calculateDiceRoll, randomDecimal } from './random' + +/** + * @description Random Helpers + * These functions focus on randomness + */ +describe('[Botmation] helpers/random', () => { + + // + // Unit Test + it('calculateDiceRoll() returns a random number from 1 to n where n is the numberOfDiceSides', () => { + const noParams = calculateDiceRoll() + const diceWithTwoSides = calculateDiceRoll(2) + const diceWithNineSides = calculateDiceRoll(9) + const diceWithTwentyFiveSides = calculateDiceRoll(25) + + expect(noParams).toEqual(1) + + expect(diceWithTwoSides).toBeGreaterThanOrEqual(1) + expect(diceWithTwoSides).toBeLessThanOrEqual(2) + + expect(diceWithNineSides).toBeGreaterThanOrEqual(1) + expect(diceWithNineSides).toBeLessThanOrEqual(9) + + expect(diceWithTwentyFiveSides).toBeGreaterThanOrEqual(1) + expect(diceWithTwentyFiveSides).toBeLessThanOrEqual(25) + + // unique edge cases + const diceWithZeroSides = calculateDiceRoll(0) + const diceWithNegativeOneSide = calculateDiceRoll(-1) + const diceWithNegativeSides = calculateDiceRoll(-10) + + expect(diceWithZeroSides).toEqual(0) + expect(diceWithNegativeOneSide).toEqual(0) + expect(diceWithNegativeSides).toEqual(0) + }) + + it('randomDecimal() returns a pseudorandom decimal between 0 to 1', () => { + for(let i = 0; i < 100; i++) { + const randomDecimalValue = randomDecimal() + expect(randomDecimalValue).toBeGreaterThanOrEqual(0) + expect(randomDecimalValue).toBeLessThanOrEqual(1) + } + }) + +}) diff --git a/libs/core/src/lib/helpers/random.ts b/libs/core/src/lib/helpers/random.ts new file mode 100644 index 000000000..54e44a1b4 --- /dev/null +++ b/libs/core/src/lib/helpers/random.ts @@ -0,0 +1,16 @@ +/** + * Roll a dice with the given number of sides + * @param numberOfDiceSides n where n >= 1 + * if n is 0 or negative, then return value is 0 + * @return 1 -> n (including n) + */ +export const calculateDiceRoll = (numberOfDiceSides = 1): number => + numberOfDiceSides >= 1 ? + Math.floor(randomDecimal() * numberOfDiceSides) + 1 : + 0 + +/** + * @return decimal 0-1 + */ +export const randomDecimal = (): number => + Math.random() From ccc660a056b4cf7ce89bace46ee1a5016af736ae Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:16:02 -0600 Subject: [PATCH 25/54] naming - rollDice()() botAction - randomDiceRoll() helper similar to randomDecimal() helper --- libs/core/src/lib/actions/random.ts | 7 ++----- libs/core/src/lib/helpers/random.spec.ts | 18 +++++++++--------- libs/core/src/lib/helpers/random.ts | 6 +++--- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 7a47ee82c..26ae67bc0 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -1,6 +1,6 @@ import { BotAction } from '../interfaces/bot-actions' import { assemblyLine } from './assembly-lines' -import { calculateDiceRoll, randomDecimal } from '../helpers/random' +import { randomDiceRoll, randomDecimal } from '../helpers/random' /** * Run assembled BotActions ONLY if a virtually rolled dice lands on the number to roll @@ -13,14 +13,11 @@ export const rollDice = (numberOfDiceSides = 1, numberToRoll = 1) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { - if (calculateDiceRoll(numberOfDiceSides) === numberToRoll) { + if (randomDiceRoll(numberOfDiceSides) === numberToRoll) { return assemblyLine()(...actions)(page, ...injects) } } -// todo problem about this design -> it limits the range of "random" to 50% or smaller -// if dev wants to run BotActions with a 60% chance or higher, this won't work - /** * Run assembled BotActions based on a probability * if random number generated is within the probability range, then assembled BotActions are ran diff --git a/libs/core/src/lib/helpers/random.spec.ts b/libs/core/src/lib/helpers/random.spec.ts index f15a62dc6..6fb8af81b 100644 --- a/libs/core/src/lib/helpers/random.spec.ts +++ b/libs/core/src/lib/helpers/random.spec.ts @@ -1,4 +1,4 @@ -import { calculateDiceRoll, randomDecimal } from './random' +import { randomDiceRoll, randomDecimal } from './random' /** * @description Random Helpers @@ -8,11 +8,11 @@ describe('[Botmation] helpers/random', () => { // // Unit Test - it('calculateDiceRoll() returns a random number from 1 to n where n is the numberOfDiceSides', () => { - const noParams = calculateDiceRoll() - const diceWithTwoSides = calculateDiceRoll(2) - const diceWithNineSides = calculateDiceRoll(9) - const diceWithTwentyFiveSides = calculateDiceRoll(25) + it('randomDiceRoll() returns a random number from 1 to n where n is the numberOfDiceSides', () => { + const noParams = randomDiceRoll() + const diceWithTwoSides = randomDiceRoll(2) + const diceWithNineSides = randomDiceRoll(9) + const diceWithTwentyFiveSides = randomDiceRoll(25) expect(noParams).toEqual(1) @@ -26,9 +26,9 @@ describe('[Botmation] helpers/random', () => { expect(diceWithTwentyFiveSides).toBeLessThanOrEqual(25) // unique edge cases - const diceWithZeroSides = calculateDiceRoll(0) - const diceWithNegativeOneSide = calculateDiceRoll(-1) - const diceWithNegativeSides = calculateDiceRoll(-10) + const diceWithZeroSides = randomDiceRoll(0) + const diceWithNegativeOneSide = randomDiceRoll(-1) + const diceWithNegativeSides = randomDiceRoll(-10) expect(diceWithZeroSides).toEqual(0) expect(diceWithNegativeOneSide).toEqual(0) diff --git a/libs/core/src/lib/helpers/random.ts b/libs/core/src/lib/helpers/random.ts index 54e44a1b4..c72196b28 100644 --- a/libs/core/src/lib/helpers/random.ts +++ b/libs/core/src/lib/helpers/random.ts @@ -1,16 +1,16 @@ /** - * Roll a dice with the given number of sides + * Roll a virtual dice with n number of sides then return the side's number that it lands on * @param numberOfDiceSides n where n >= 1 * if n is 0 or negative, then return value is 0 * @return 1 -> n (including n) */ -export const calculateDiceRoll = (numberOfDiceSides = 1): number => +export const randomDiceRoll = (numberOfDiceSides = 1): number => numberOfDiceSides >= 1 ? Math.floor(randomDecimal() * numberOfDiceSides) + 1 : 0 /** - * @return decimal 0-1 + * @return decimal between 0-1 */ export const randomDecimal = (): number => Math.random() From b583aed098f1bced85f0706c489280347a910a2a Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:24:52 -0600 Subject: [PATCH 26/54] rollDice()() implementation to use probably()() --- libs/core/src/lib/actions/random.spec.ts | 4 ++-- libs/core/src/lib/actions/random.ts | 16 ++++++---------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/libs/core/src/lib/actions/random.spec.ts b/libs/core/src/lib/actions/random.spec.ts index 77b8ac29a..3d812b7d7 100644 --- a/libs/core/src/lib/actions/random.spec.ts +++ b/libs/core/src/lib/actions/random.spec.ts @@ -19,8 +19,8 @@ describe('[Botmation] actions/random', () => { }) // - // diceRoll() Unit Tests - it('diceRoll() should roll dice and if the correct number to roll appears, than the assembled actions are ran, otherwise the assembled actions are skipped', async() => { + // rollDice() Unit Tests + it('rollDice() should roll dice and if the correct number to roll appears, than the assembled actions are ran, otherwise the assembled actions are skipped', async() => { }) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 26ae67bc0..9d291a6fd 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -1,27 +1,23 @@ import { BotAction } from '../interfaces/bot-actions' import { assemblyLine } from './assembly-lines' -import { randomDiceRoll, randomDecimal } from '../helpers/random' +import { randomDecimal } from '../helpers/random' /** - * Run assembled BotActions ONLY if a virtually rolled dice lands on the number to roll - * This works for probabilitilities of 50% or less. For higher probabilities, use the probably()() BotAction - * @future once sync BotActions are supported, update with givenThat()() using a function wrapper to convert diceRoll() into a boolean expression (dice roll correct number or not) + * Roll a virtual device with number of dice sides (default is 1). If the dice rolls a 1, the assembled BotActions run + * Works for probabilitilities of 50% or less (dice with 2 sides or more). For higher probabilities, use probably()() directly * @param numberOfDiceSides * @param numberToRoll */ export const rollDice = - (numberOfDiceSides = 1, numberToRoll = 1) => + (numberOfDiceSides = 1) => (...actions: BotAction[]): BotAction => - async(page, ...injects) => { - if (randomDiceRoll(numberOfDiceSides) === numberToRoll) { - return assemblyLine()(...actions)(page, ...injects) - } - } + probably(1 / numberOfDiceSides)(...actions) /** * Run assembled BotActions based on a probability * if random number generated is within the probability range, then assembled BotActions are ran * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing + * @future use givenThat() with a wrapper function post v2 (support sync BotActions and ) * @param probability number 0-1 ie .6 = 60% */ export const probably = From 7c2467c71cda7fc016b2c0e149566a1810c18f2a Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:26:07 -0600 Subject: [PATCH 27/54] delete randomDiceRoll() - no longer necessary --- libs/core/src/lib/helpers/random.spec.ts | 31 +----------------------- libs/core/src/lib/helpers/random.ts | 11 --------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/libs/core/src/lib/helpers/random.spec.ts b/libs/core/src/lib/helpers/random.spec.ts index 6fb8af81b..413ed234c 100644 --- a/libs/core/src/lib/helpers/random.spec.ts +++ b/libs/core/src/lib/helpers/random.spec.ts @@ -1,4 +1,4 @@ -import { randomDiceRoll, randomDecimal } from './random' +import { randomDecimal } from './random' /** * @description Random Helpers @@ -6,35 +6,6 @@ import { randomDiceRoll, randomDecimal } from './random' */ describe('[Botmation] helpers/random', () => { - // - // Unit Test - it('randomDiceRoll() returns a random number from 1 to n where n is the numberOfDiceSides', () => { - const noParams = randomDiceRoll() - const diceWithTwoSides = randomDiceRoll(2) - const diceWithNineSides = randomDiceRoll(9) - const diceWithTwentyFiveSides = randomDiceRoll(25) - - expect(noParams).toEqual(1) - - expect(diceWithTwoSides).toBeGreaterThanOrEqual(1) - expect(diceWithTwoSides).toBeLessThanOrEqual(2) - - expect(diceWithNineSides).toBeGreaterThanOrEqual(1) - expect(diceWithNineSides).toBeLessThanOrEqual(9) - - expect(diceWithTwentyFiveSides).toBeGreaterThanOrEqual(1) - expect(diceWithTwentyFiveSides).toBeLessThanOrEqual(25) - - // unique edge cases - const diceWithZeroSides = randomDiceRoll(0) - const diceWithNegativeOneSide = randomDiceRoll(-1) - const diceWithNegativeSides = randomDiceRoll(-10) - - expect(diceWithZeroSides).toEqual(0) - expect(diceWithNegativeOneSide).toEqual(0) - expect(diceWithNegativeSides).toEqual(0) - }) - it('randomDecimal() returns a pseudorandom decimal between 0 to 1', () => { for(let i = 0; i < 100; i++) { const randomDecimalValue = randomDecimal() diff --git a/libs/core/src/lib/helpers/random.ts b/libs/core/src/lib/helpers/random.ts index c72196b28..23af09b1b 100644 --- a/libs/core/src/lib/helpers/random.ts +++ b/libs/core/src/lib/helpers/random.ts @@ -1,14 +1,3 @@ -/** - * Roll a virtual dice with n number of sides then return the side's number that it lands on - * @param numberOfDiceSides n where n >= 1 - * if n is 0 or negative, then return value is 0 - * @return 1 -> n (including n) - */ -export const randomDiceRoll = (numberOfDiceSides = 1): number => - numberOfDiceSides >= 1 ? - Math.floor(randomDecimal() * numberOfDiceSides) + 1 : - 0 - /** * @return decimal between 0-1 */ From 036fe6aafb497b84899d6640077a2b0b7e432659 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:29:30 -0600 Subject: [PATCH 28/54] added ability to override random decimal function - if prefer not pseudo random ie random derived via buffer/crypto then set your own function in the helper --- libs/core/src/lib/actions/random.ts | 2 +- libs/core/src/lib/helpers/random.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 9d291a6fd..833765bf8 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -18,7 +18,7 @@ export const rollDice = * if random number generated is within the probability range, then assembled BotActions are ran * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing * @future use givenThat() with a wrapper function post v2 (support sync BotActions and ) - * @param probability number 0-1 ie .6 = 60% + * @param probability decimal between 0-1 ie .6 = 60% probability of assembled BotActions running */ export const probably = (probability = 1) => diff --git a/libs/core/src/lib/helpers/random.ts b/libs/core/src/lib/helpers/random.ts index 23af09b1b..6d3fec70f 100644 --- a/libs/core/src/lib/helpers/random.ts +++ b/libs/core/src/lib/helpers/random.ts @@ -1,5 +1,5 @@ /** * @return decimal between 0-1 */ -export const randomDecimal = (): number => - Math.random() +export const randomDecimal = (randomDecimalGenerator = Math.random): number => + randomDecimalGenerator() From 4921569cbc9bc369f53f8bbbf9982bd2dd75e403 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:36:03 -0600 Subject: [PATCH 29/54] probably()() HO 2nd param (optional) to override random number generator - instead of using pseudo random number generating, use a buffer/crypto/other --- libs/core/src/lib/actions/random.ts | 5 +++-- libs/core/src/lib/helpers/random.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 833765bf8..048c146a4 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -19,12 +19,13 @@ export const rollDice = * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing * @future use givenThat() with a wrapper function post v2 (support sync BotActions and ) * @param probability decimal between 0-1 ie .6 = 60% probability of assembled BotActions running + * @param generateRandomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method */ export const probably = - (probability = 1) => + (probability = 1, generateRandomDecimal = randomDecimal) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { - if (randomDecimal() <= probability) { + if (generateRandomDecimal() <= probability) { return assemblyLine()(...actions)(page, ...injects) } } diff --git a/libs/core/src/lib/helpers/random.ts b/libs/core/src/lib/helpers/random.ts index 6d3fec70f..23af09b1b 100644 --- a/libs/core/src/lib/helpers/random.ts +++ b/libs/core/src/lib/helpers/random.ts @@ -1,5 +1,5 @@ /** * @return decimal between 0-1 */ -export const randomDecimal = (randomDecimalGenerator = Math.random): number => - randomDecimalGenerator() +export const randomDecimal = (): number => + Math.random() From b5da118c1a2188fb4b069b37bbbdf9600f954d5b Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:37:35 -0600 Subject: [PATCH 30/54] refine test expectations --- libs/core/src/lib/helpers/random.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/core/src/lib/helpers/random.spec.ts b/libs/core/src/lib/helpers/random.spec.ts index 413ed234c..cb676f3fc 100644 --- a/libs/core/src/lib/helpers/random.spec.ts +++ b/libs/core/src/lib/helpers/random.spec.ts @@ -9,8 +9,8 @@ describe('[Botmation] helpers/random', () => { it('randomDecimal() returns a pseudorandom decimal between 0 to 1', () => { for(let i = 0; i < 100; i++) { const randomDecimalValue = randomDecimal() - expect(randomDecimalValue).toBeGreaterThanOrEqual(0) - expect(randomDecimalValue).toBeLessThanOrEqual(1) + expect(randomDecimalValue).toBeGreaterThan(0) + expect(randomDecimalValue).toBeLessThan(1) } }) From 42f9feb517bca2b9480fd3306eb8d926dbc893d8 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 16 Mar 2021 21:45:22 -0600 Subject: [PATCH 31/54] notes --- libs/core/src/lib/actions/random.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 048c146a4..97188b542 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -17,8 +17,8 @@ export const rollDice = * Run assembled BotActions based on a probability * if random number generated is within the probability range, then assembled BotActions are ran * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing - * @future use givenThat() with a wrapper function post v2 (support sync BotActions and ) - * @param probability decimal between 0-1 ie .6 = 60% probability of assembled BotActions running + * @future use givenThat() with a wrapper function post v2 (support sync BotActions and decomposed `page` from params into an inject) + * @param probability represented as a decimal ie .6 = 60% chance of running assembled BotActions * @param generateRandomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method */ export const probably = From 95505e95fc6d8b73eab5dd70f817bf7eb56054f3 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 17 Mar 2021 18:11:28 -0600 Subject: [PATCH 32/54] [wip] randomGenerator HO BotAction to inject randomizer func --- libs/core/src/lib/actions/random.ts | 18 +++++++++++++++- libs/core/src/lib/actions/scrapers.ts | 31 +++++++++++++-------------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 97188b542..1ac9a5561 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -1,6 +1,21 @@ import { BotAction } from '../interfaces/bot-actions' -import { assemblyLine } from './assembly-lines' +import { assemblyLine, pipe } from './assembly-lines' import { randomDecimal } from '../helpers/random' +import { inject } from './inject' +import { errors } from './errors' + +/** + * @description Inject a random decimal number generator + * + * @param generateRandomDecimalFunction + */ +export const randomGenerator = (generateRandomDecimalFunction: Function) => + (...actions: BotAction[]): BotAction => + pipe()( + inject(generateRandomDecimalFunction)( + errors('randomGenerator()()')(...actions) + ) + ) /** * Roll a virtual device with number of dice sides (default is 1). If the dice rolls a 1, the assembled BotActions run @@ -29,3 +44,4 @@ export const probably = return assemblyLine()(...actions)(page, ...injects) } } + diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index 5e4a144f3..fd5313190 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -12,20 +12,6 @@ import { errors } from '../actions/errors' import { elementExistsInDocument, getElementOuterHTML, getElementsOuterHTML, textExistsInDocument } from '../helpers/scrapers' import { unpipeInjects } from '../helpers/pipe' import { pipe } from './assembly-lines' -import { map } from './pipe' - -/** - * @description Inject htmlParser for ScraperBotAction's - * - * @param htmlParserFunction - */ -export const htmlParser = (htmlParserFunction: Function) => - (...actions: BotAction[]): BotAction => - pipe()( - inject(htmlParserFunction)( - errors('htmlParser()()')(...actions) - ) - ) /** * A ConditionalBotAction to return a Boolean value @@ -33,14 +19,27 @@ export const htmlParser = (htmlParserFunction: Function) => * @param elementSelector */ export const elementExists = (elementSelector: string): ConditionalBotAction => - evaluate(elementExistsInDocument, elementSelector) +evaluate(elementExistsInDocument, elementSelector) /** * * @param text */ export const textExists = (text: string): ConditionalBotAction => - evaluate(textExistsInDocument, text) +evaluate(textExistsInDocument, text) + +/** + * @description Inject htmlParser for ScraperBotAction's + * + * @param htmlParserFunction + */ +export const htmlParser = (htmlParserFunction: Function) => + (...actions: BotAction[]): BotAction => + pipe()( + inject(htmlParserFunction)( + errors('htmlParser()()')(...actions) + ) + ) /** * Returns the first Element that matches the provided HTML Selector From 510827995df25064cbba911519f5fe2c4a4e3af2 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 17 Mar 2021 18:22:25 -0600 Subject: [PATCH 33/54] [concept] probably()() accepts inject for random number function --- libs/core/src/lib/actions/random.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 1ac9a5561..5fe5bc592 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -24,9 +24,9 @@ export const randomGenerator = (generateRandomDecimalFunction: Function) => * @param numberToRoll */ export const rollDice = - (numberOfDiceSides = 1) => + (numberOfDiceSides = 1, generateRandomDecimal?: () => number) => (...actions: BotAction[]): BotAction => - probably(1 / numberOfDiceSides)(...actions) + probably(1 / numberOfDiceSides, generateRandomDecimal)(...actions) /** * Run assembled BotActions based on a probability @@ -37,9 +37,17 @@ export const rollDice = * @param generateRandomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method */ export const probably = - (probability = 1, generateRandomDecimal = randomDecimal) => + (probability = .6, generateRandomDecimal?: () => number) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { + if (!generateRandomDecimal) { + if (typeof injects[0] === 'function') { + generateRandomDecimal = injects[0] // once injects becomes Map based :) + } else { + generateRandomDecimal = randomDecimal + } + } + if (generateRandomDecimal() <= probability) { return assemblyLine()(...actions)(page, ...injects) } From ac74747f560b1169117f891ffa10d1e2c2c23e93 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 17 Mar 2021 18:23:39 -0600 Subject: [PATCH 34/54] use unpipeInjects() helper - very much looking forward to a key based injects system --- libs/core/src/lib/actions/random.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 5fe5bc592..f2af5e5e8 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -3,6 +3,7 @@ import { assemblyLine, pipe } from './assembly-lines' import { randomDecimal } from '../helpers/random' import { inject } from './inject' import { errors } from './errors' +import { unpipeInjects } from '../helpers/pipe' /** * @description Inject a random decimal number generator @@ -41,8 +42,10 @@ export const probably = (...actions: BotAction[]): BotAction => async(page, ...injects) => { if (!generateRandomDecimal) { - if (typeof injects[0] === 'function') { - generateRandomDecimal = injects[0] // once injects becomes Map based :) + const [,injectedRandomDecimalGenerator] = unpipeInjects(injects, 1) + + if (typeof injectedRandomDecimalGenerator === 'function') { + generateRandomDecimal = injectedRandomDecimalGenerator // once injects becomes Map based :) } else { generateRandomDecimal = randomDecimal } From a3ccdb4dc3c4536b1307f1d39c85bd0963994e1c Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 17 Mar 2021 18:31:02 -0600 Subject: [PATCH 35/54] concept set randomDecimal HO injector for NumberReturningFunc --- libs/core/src/index.ts | 2 ++ libs/core/src/lib/actions/random.ts | 25 +++++++++++++------------ libs/core/src/lib/helpers/random.ts | 2 +- libs/core/src/lib/types/index.ts | 1 + libs/core/src/lib/types/random.ts | 2 ++ 5 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 libs/core/src/lib/types/random.ts diff --git a/libs/core/src/index.ts b/libs/core/src/index.ts index 640cee079..a5e48cb39 100644 --- a/libs/core/src/index.ts +++ b/libs/core/src/index.ts @@ -28,6 +28,7 @@ export * from './lib/actions/scrapers' export * from './lib/actions/loops' export * from './lib/actions/branching' export * from './lib/actions/time' +export * from './lib/actions/random' // // Helpers @@ -42,3 +43,4 @@ export * from './lib/helpers/navigation' export * from './lib/helpers/pipe' export * from './lib/helpers/scrapers' export * from './lib/helpers/cases' +export * from './lib/helpers/random' diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index f2af5e5e8..935859b8b 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -1,19 +1,20 @@ import { BotAction } from '../interfaces/bot-actions' import { assemblyLine, pipe } from './assembly-lines' -import { randomDecimal } from '../helpers/random' +import { generateRandomDecimal } from '../helpers/random' import { inject } from './inject' import { errors } from './errors' import { unpipeInjects } from '../helpers/pipe' +import { NumberReturningFunc } from '../types/random' /** * @description Inject a random decimal number generator * - * @param generateRandomDecimalFunction + * @param injectGenerateRandomDecimalFunction */ -export const randomGenerator = (generateRandomDecimalFunction: Function) => +export const randomDecimal = (injectGenerateRandomDecimalFunction: NumberReturningFunc) => (...actions: BotAction[]): BotAction => pipe()( - inject(generateRandomDecimalFunction)( + inject(injectGenerateRandomDecimalFunction)( errors('randomGenerator()()')(...actions) ) ) @@ -25,9 +26,9 @@ export const randomGenerator = (generateRandomDecimalFunction: Function) => * @param numberToRoll */ export const rollDice = - (numberOfDiceSides = 1, generateRandomDecimal?: () => number) => + (numberOfDiceSides = 1, overloadGenerateRandomDecimal?: NumberReturningFunc) => (...actions: BotAction[]): BotAction => - probably(1 / numberOfDiceSides, generateRandomDecimal)(...actions) + probably(1 / numberOfDiceSides, overloadGenerateRandomDecimal)(...actions) /** * Run assembled BotActions based on a probability @@ -35,23 +36,23 @@ export const rollDice = * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing * @future use givenThat() with a wrapper function post v2 (support sync BotActions and decomposed `page` from params into an inject) * @param probability represented as a decimal ie .6 = 60% chance of running assembled BotActions - * @param generateRandomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method + * @param overloadGenerateRandomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method */ export const probably = - (probability = .6, generateRandomDecimal?: () => number) => + (probability = .6, overloadGenerateRandomDecimal?: NumberReturningFunc) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { - if (!generateRandomDecimal) { + if (!overloadGenerateRandomDecimal) { const [,injectedRandomDecimalGenerator] = unpipeInjects(injects, 1) if (typeof injectedRandomDecimalGenerator === 'function') { - generateRandomDecimal = injectedRandomDecimalGenerator // once injects becomes Map based :) + overloadGenerateRandomDecimal = injectedRandomDecimalGenerator // once injects becomes Map based :) } else { - generateRandomDecimal = randomDecimal + overloadGenerateRandomDecimal = generateRandomDecimal } } - if (generateRandomDecimal() <= probability) { + if (overloadGenerateRandomDecimal() <= probability) { return assemblyLine()(...actions)(page, ...injects) } } diff --git a/libs/core/src/lib/helpers/random.ts b/libs/core/src/lib/helpers/random.ts index 23af09b1b..6595e273e 100644 --- a/libs/core/src/lib/helpers/random.ts +++ b/libs/core/src/lib/helpers/random.ts @@ -1,5 +1,5 @@ /** * @return decimal between 0-1 */ -export const randomDecimal = (): number => +export const generateRandomDecimal = (): number => Math.random() diff --git a/libs/core/src/lib/types/index.ts b/libs/core/src/lib/types/index.ts index db1d39722..276fda85a 100644 --- a/libs/core/src/lib/types/index.ts +++ b/libs/core/src/lib/types/index.ts @@ -6,3 +6,4 @@ export * from './pipe-value' export * from './abort-line-signal' export * from './callbacks' export * from './cases' +export * from './random' diff --git a/libs/core/src/lib/types/random.ts b/libs/core/src/lib/types/random.ts new file mode 100644 index 000000000..424a8145f --- /dev/null +++ b/libs/core/src/lib/types/random.ts @@ -0,0 +1,2 @@ + +export type NumberReturningFunc = () => number From 673cbe3d9c44256ed2fc807d7b08d2c267d27eac Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 17 Mar 2021 18:32:37 -0600 Subject: [PATCH 36/54] not going to force pipe() on randomDecimal()() injector - similar to files()(), because indexedDB has botactions that rely on piping, and the context might be a chain, so with that you want to enforce pipe because it makes sense (however arugably oppose it can be said) --- libs/core/src/lib/actions/random.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 935859b8b..d1778d981 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -1,5 +1,5 @@ import { BotAction } from '../interfaces/bot-actions' -import { assemblyLine, pipe } from './assembly-lines' +import { assemblyLine } from './assembly-lines' import { generateRandomDecimal } from '../helpers/random' import { inject } from './inject' import { errors } from './errors' @@ -13,10 +13,8 @@ import { NumberReturningFunc } from '../types/random' */ export const randomDecimal = (injectGenerateRandomDecimalFunction: NumberReturningFunc) => (...actions: BotAction[]): BotAction => - pipe()( - inject(injectGenerateRandomDecimalFunction)( - errors('randomGenerator()()')(...actions) - ) + inject(injectGenerateRandomDecimalFunction)( + errors('randomGenerator()()')(...actions) ) /** From 4bc7547ed45ba46cad66192bcbae6b55e009eb1c Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 17 Mar 2021 18:49:46 -0600 Subject: [PATCH 37/54] consistent naming --- libs/core/src/lib/actions/random.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index d1778d981..31c21b980 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -14,7 +14,7 @@ import { NumberReturningFunc } from '../types/random' export const randomDecimal = (injectGenerateRandomDecimalFunction: NumberReturningFunc) => (...actions: BotAction[]): BotAction => inject(injectGenerateRandomDecimalFunction)( - errors('randomGenerator()()')(...actions) + errors('randomDecimal()()')(...actions) ) /** @@ -41,10 +41,10 @@ export const probably = (...actions: BotAction[]): BotAction => async(page, ...injects) => { if (!overloadGenerateRandomDecimal) { - const [,injectedRandomDecimalGenerator] = unpipeInjects(injects, 1) + const [,injectedRandomDecimalFunction] = unpipeInjects(injects, 1) - if (typeof injectedRandomDecimalGenerator === 'function') { - overloadGenerateRandomDecimal = injectedRandomDecimalGenerator // once injects becomes Map based :) + if (typeof injectedRandomDecimalFunction === 'function') { + overloadGenerateRandomDecimal = injectedRandomDecimalFunction // once injects becomes Map based :) } else { overloadGenerateRandomDecimal = generateRandomDecimal } From 653ea141e0c3b8b3645c7c924ff33a74a6986d00 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Thu, 18 Mar 2021 21:17:38 -0600 Subject: [PATCH 38/54] [wip] unit & integration tests - BotActions group Random: randomDecimal()(), rollDice()(), probably()() - not complete coverage --- libs/core/src/lib/actions/random.spec.ts | 104 ++++++++++++++++++++++- libs/core/src/lib/helpers/random.spec.ts | 10 +-- 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/libs/core/src/lib/actions/random.spec.ts b/libs/core/src/lib/actions/random.spec.ts index 3d812b7d7..953789ea0 100644 --- a/libs/core/src/lib/actions/random.spec.ts +++ b/libs/core/src/lib/actions/random.spec.ts @@ -1,11 +1,44 @@ import { Page } from 'puppeteer' +import { probably, randomDecimal, rollDice } from './random' + +jest.mock('./inject', () => { + // Require the original module to not be mocked... + const originalModule = jest.requireActual('./inject') + + return { + ...originalModule, + inject: jest.fn(() => () => () => {}) + } +}) + +jest.mock('./errors', () => { + // Require the original module to not be mocked... + const originalModule = jest.requireActual('./errors') + + return { + ...originalModule, + errors: jest.fn(() => () => () => {}) + } +}) + +jest.mock('../helpers/random', () => { + // Require the original module to not be mocked... + const originalModule = jest.requireActual('../helpers/random') + + return { + ...originalModule, + generateRandomDecimal: jest.fn(() => .45) + } +}) + /** * @description Random BotActions */ describe('[Botmation] actions/random', () => { let mockPage: Page + let mockAction, mockAction2 beforeEach(() => { mockPage = { @@ -16,12 +49,81 @@ describe('[Botmation] actions/random', () => { url: jest.fn(() => ''), goto: jest.fn() } as any as Page + + mockAction = jest.fn(() => Promise.resolve()) + mockAction2 = jest.fn(() => Promise.resolve()) + }) + + // Clean up + afterAll(async() => { + jest.unmock('./inject') + jest.unmock('./errors') + jest.unmock('../helpers/random') + }) + + // + // randomDecimal() integration test + it('randomDecimal()() should integrate with inject()() and errors()() in order to assemble functionality of providing a generate random decimal function', async() => { + const randomDecimalInjectedFunction = () => .78 + + await randomDecimal(randomDecimalInjectedFunction)()(mockPage) + + const {inject: mockInjectMethod} = require('./inject') + const {errors: mockErrorsMethod} = require('./errors') + + expect(mockInjectMethod).toHaveBeenCalledTimes(1) + expect(mockInjectMethod).toHaveBeenCalledWith(expect.any(Function)) + + expect(mockErrorsMethod).toHaveBeenCalledTimes(1) + expect(mockErrorsMethod).toHaveBeenCalledWith('randomDecimal()()') }) // - // rollDice() Unit Tests + // rollDice() integration test it('rollDice() should roll dice and if the correct number to roll appears, than the assembled actions are ran, otherwise the assembled actions are skipped', async() => { + // actions will run + await rollDice(2)(mockAction, mockAction2)(mockPage) + + expect(mockAction).toHaveBeenCalled() + expect(mockAction2).toHaveBeenCalled() + + // actions wont run + await rollDice(20)(mockAction, mockAction2)(mockPage) + + expect(mockAction).toHaveBeenCalledTimes(1) + expect(mockAction2).toHaveBeenCalledTimes(1) + }) + + // + // probably()() unit test + it('probably()() should calculate a random decimal and if its less than or equal to the probability provided, then run the assembled BotActions otherwise do not run them', async() => { + await probably(.36)(mockAction, mockAction2)(mockPage) + + expect(mockAction).not.toHaveBeenCalled() + expect(mockAction2).not.toHaveBeenCalled() + + await probably(.87)(mockAction, mockAction2)(mockPage) + + expect(mockAction).toHaveBeenCalledTimes(1) + expect(mockAction2).toHaveBeenCalledTimes(1) + }) + + it('probably()() has a default helper method with pseudorandom decimal generator and supports overloading that with an injected function or HO function. HO function has precedence over injected.', async() => { + const injectedRandomFunction = jest.fn(() => .8) + const higherOrderRandomFunction = jest.fn(() => .05) + + await probably(.12)(mockAction, mockAction2)(mockPage, injectedRandomFunction) + + expect(injectedRandomFunction).toHaveBeenCalledTimes(1) + expect(mockAction).not.toHaveBeenCalled() + expect(mockAction2).not.toHaveBeenCalled() + + await probably(.12, higherOrderRandomFunction)(mockAction, mockAction2)(mockPage, injectedRandomFunction) + expect(higherOrderRandomFunction).toHaveBeenCalledTimes(1) + expect(injectedRandomFunction).toHaveBeenCalledTimes(1) + expect(mockAction).toHaveBeenCalled() + expect(mockAction2).toHaveBeenCalled() }) }) diff --git a/libs/core/src/lib/helpers/random.spec.ts b/libs/core/src/lib/helpers/random.spec.ts index cb676f3fc..8094734ff 100644 --- a/libs/core/src/lib/helpers/random.spec.ts +++ b/libs/core/src/lib/helpers/random.spec.ts @@ -1,4 +1,4 @@ -import { randomDecimal } from './random' +import { generateRandomDecimal } from './random' /** * @description Random Helpers @@ -6,11 +6,11 @@ import { randomDecimal } from './random' */ describe('[Botmation] helpers/random', () => { - it('randomDecimal() returns a pseudorandom decimal between 0 to 1', () => { + it('generateRandomDecimal() returns a pseudorandom decimal between 0 to 1', () => { for(let i = 0; i < 100; i++) { - const randomDecimalValue = randomDecimal() - expect(randomDecimalValue).toBeGreaterThan(0) - expect(randomDecimalValue).toBeLessThan(1) + const generateRandomDecimalValue = generateRandomDecimal() + expect(generateRandomDecimalValue).toBeGreaterThan(0) + expect(generateRandomDecimalValue).toBeLessThan(1) } }) From 24b0b710b919c48357db7bd6383ef60b1a9fb824 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 22 Mar 2021 19:31:01 -0600 Subject: [PATCH 39/54] random botactions test coverage --- libs/core/src/lib/actions/random.spec.ts | 90 +++++++++++++++--------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/libs/core/src/lib/actions/random.spec.ts b/libs/core/src/lib/actions/random.spec.ts index 953789ea0..d715cce20 100644 --- a/libs/core/src/lib/actions/random.spec.ts +++ b/libs/core/src/lib/actions/random.spec.ts @@ -1,6 +1,5 @@ import { Page } from 'puppeteer' - -import { probably, randomDecimal, rollDice } from './random' +import { randomDecimal, rollDice, probably } from './random' jest.mock('./inject', () => { // Require the original module to not be mocked... @@ -8,7 +7,7 @@ jest.mock('./inject', () => { return { ...originalModule, - inject: jest.fn(() => () => () => {}) + inject: jest.fn(originalModule.inject) } }) @@ -18,7 +17,7 @@ jest.mock('./errors', () => { return { ...originalModule, - errors: jest.fn(() => () => () => {}) + errors: jest.fn(originalModule.errors) } }) @@ -31,7 +30,6 @@ jest.mock('../helpers/random', () => { generateRandomDecimal: jest.fn(() => .45) } }) - /** * @description Random BotActions */ @@ -61,20 +59,65 @@ describe('[Botmation] actions/random', () => { jest.unmock('../helpers/random') }) + // + // probably()() unit test + it('probably()() should calculate a random decimal and if its less than or equal to the probability provided, then run the assembled BotActions otherwise do not run them', async() => { + await probably(.36)(mockAction, mockAction2)(mockPage) + + expect(mockAction).not.toHaveBeenCalled() + expect(mockAction2).not.toHaveBeenCalled() + + await probably(.87)(mockAction, mockAction2)(mockPage) + + expect(mockAction).toHaveBeenCalledTimes(1) + expect(mockAction2).toHaveBeenCalledTimes(1) + + const mockOverloadGenerateRandomDecimal = jest.fn(() => 0.9) + await probably(.3, mockOverloadGenerateRandomDecimal)(mockAction, mockAction2)(mockPage) + + expect(mockOverloadGenerateRandomDecimal).toHaveBeenNthCalledWith(1) + expect(mockAction).toHaveBeenCalledTimes(1) + expect(mockAction2).toHaveBeenCalledTimes(1) + + await probably()(mockAction, mockAction2)(mockPage) + + expect(mockAction).toHaveBeenCalledTimes(2) + expect(mockAction2).toHaveBeenCalledTimes(2) + }) + + it('probably()() has a default helper method with pseudorandom decimal generator and supports overloading that with an injected function or HO function. HO function has precedence over injected.', async() => { + const injectedRandomFunction = jest.fn(() => .8) + const higherOrderRandomFunction = jest.fn(() => .05) + + await probably(.12)(mockAction, mockAction2)(mockPage, injectedRandomFunction) + + expect(injectedRandomFunction).toHaveBeenCalledTimes(1) + expect(mockAction).not.toHaveBeenCalled() + expect(mockAction2).not.toHaveBeenCalled() + + await probably(.12, higherOrderRandomFunction)(mockAction, mockAction2)(mockPage, injectedRandomFunction) + + expect(higherOrderRandomFunction).toHaveBeenCalledTimes(1) + expect(injectedRandomFunction).toHaveBeenCalledTimes(1) + expect(mockAction).toHaveBeenCalled() + expect(mockAction2).toHaveBeenCalled() + }) + // // randomDecimal() integration test it('randomDecimal()() should integrate with inject()() and errors()() in order to assemble functionality of providing a generate random decimal function', async() => { - const randomDecimalInjectedFunction = () => .78 + const randomDecimalInjectedFunction = jest.fn(() => .78) - await randomDecimal(randomDecimalInjectedFunction)()(mockPage) + await randomDecimal(randomDecimalInjectedFunction)(rollDice()())(mockPage) const {inject: mockInjectMethod} = require('./inject') const {errors: mockErrorsMethod} = require('./errors') expect(mockInjectMethod).toHaveBeenCalledTimes(1) expect(mockInjectMethod).toHaveBeenCalledWith(expect.any(Function)) + expect(randomDecimalInjectedFunction).toHaveBeenCalledTimes(1) - expect(mockErrorsMethod).toHaveBeenCalledTimes(1) + expect(mockErrorsMethod).toHaveBeenCalledTimes(1) // todo similar integration tests with other functions that rely on errors() ie files()() expect(mockErrorsMethod).toHaveBeenCalledWith('randomDecimal()()') }) @@ -92,38 +135,15 @@ describe('[Botmation] actions/random', () => { expect(mockAction).toHaveBeenCalledTimes(1) expect(mockAction2).toHaveBeenCalledTimes(1) - }) - // - // probably()() unit test - it('probably()() should calculate a random decimal and if its less than or equal to the probability provided, then run the assembled BotActions otherwise do not run them', async() => { - await probably(.36)(mockAction, mockAction2)(mockPage) - - expect(mockAction).not.toHaveBeenCalled() - expect(mockAction2).not.toHaveBeenCalled() + // + const mockOverloadGenerateRandomDecimal = jest.fn(() => .2) - await probably(.87)(mockAction, mockAction2)(mockPage) + await rollDice(100, mockOverloadGenerateRandomDecimal)(mockAction, mockAction2)(mockPage) + expect(mockOverloadGenerateRandomDecimal).toHaveBeenCalledTimes(1) expect(mockAction).toHaveBeenCalledTimes(1) expect(mockAction2).toHaveBeenCalledTimes(1) }) - it('probably()() has a default helper method with pseudorandom decimal generator and supports overloading that with an injected function or HO function. HO function has precedence over injected.', async() => { - const injectedRandomFunction = jest.fn(() => .8) - const higherOrderRandomFunction = jest.fn(() => .05) - - await probably(.12)(mockAction, mockAction2)(mockPage, injectedRandomFunction) - - expect(injectedRandomFunction).toHaveBeenCalledTimes(1) - expect(mockAction).not.toHaveBeenCalled() - expect(mockAction2).not.toHaveBeenCalled() - - await probably(.12, higherOrderRandomFunction)(mockAction, mockAction2)(mockPage, injectedRandomFunction) - - expect(higherOrderRandomFunction).toHaveBeenCalledTimes(1) - expect(injectedRandomFunction).toHaveBeenCalledTimes(1) - expect(mockAction).toHaveBeenCalled() - expect(mockAction2).toHaveBeenCalled() - }) - }) From b1acc3c36329cb49d62bdb3df5d8636749a79819 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 24 Mar 2021 23:11:50 -0600 Subject: [PATCH 40/54] indexed-db e2e testing --- assets/server/indexed-db.html | 79 ++++++++++++++++++++ libs/core/src/lib/actions/console.spec.ts | 2 +- libs/core/src/lib/actions/indexed-db.spec.ts | 71 +++++++++++++++--- libs/core/src/lib/actions/indexed-db.ts | 3 +- libs/core/src/lib/helpers/indexed-db.ts | 3 + 5 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 assets/server/indexed-db.html diff --git a/assets/server/indexed-db.html b/assets/server/indexed-db.html new file mode 100644 index 000000000..31af3b01d --- /dev/null +++ b/assets/server/indexed-db.html @@ -0,0 +1,79 @@ + + + + Testing: IndexedDB + + + +

IndexedDB

+
    +
  • DB name: MyTestDatabase
  • +
  • Store name: TestObjectStore
  • +
  • Key: TestKey
  • +
  • Key Value: TestValue234
  • +
+ + diff --git a/libs/core/src/lib/actions/console.spec.ts b/libs/core/src/lib/actions/console.spec.ts index 409b87fcc..d30787a8b 100644 --- a/libs/core/src/lib/actions/console.spec.ts +++ b/libs/core/src/lib/actions/console.spec.ts @@ -29,7 +29,7 @@ describe('[Botmation] actions/console', () => { page = await browser.newPage() }) - afterEach(async () => { + afterEach(async() => { await page.close() }) diff --git a/libs/core/src/lib/actions/indexed-db.spec.ts b/libs/core/src/lib/actions/indexed-db.spec.ts index 9311a5c8c..76002c955 100644 --- a/libs/core/src/lib/actions/indexed-db.spec.ts +++ b/libs/core/src/lib/actions/indexed-db.spec.ts @@ -3,9 +3,14 @@ import { Page } from 'puppeteer' import { setIndexedDBValue, getIndexedDBValue, - indexedDBStore + indexedDBStore, + deleteIndexedDB } from './indexed-db' +import * as puppeteer from 'puppeteer' +import { pipe } from './assembly-lines' +import { evaluate } from './scrapers' + const mockInject3rdCall = jest.fn() jest.mock('./inject', () => { const originalModule = jest.requireActual('./inject') @@ -35,6 +40,13 @@ describe('[Botmation] actions/indexed-db', () => { const injectedPipeParamKey = 'pipe-key' const injectedPipeParamValue = 'pipe-value' + // e2e testing + const databaseName = 'MyTestDatabase'; + const databaseVersion = undefined; + const storeName = 'TestObjectStore'; + const key = 'TestKey'; + const value = 'TestValue234'; + beforeEach(() => { mockPage = { evaluate: jest.fn() @@ -154,17 +166,52 @@ describe('[Botmation] actions/indexed-db', () => { expect(mockPage.evaluate).toHaveBeenNthCalledWith(12, expect.any(Function), 'missing-db-name', undefined, 'missing-store', 'missing-key') }) - // E2E Test - // E2E test was resulting in a false fail from an error, see Issue: https://github.com/smooth-code/jest-puppeteer/issues/311 - // it('should set a key/value pair in a new Database & Store, then update that value and get it again', async() => { - // await setIndexedDBValue('a-key', 'a-value', 'a-store', 1, 'a-db')(page) - // await expect(getIndexedDBValue('a-key', 'a-store', 1, 'a-db')(page)).resolves.toEqual('a-value') - - // await setIndexedDBValue('a-key', 'b-value', 'a-store', 1, 'a-db')(page) - // await expect(getIndexedDBValue('a-key', 'a-store', 1, 'a-db')(page)).resolves.toEqual('b-value') - - // // if you were to add a new key/value pair, that would change the schema, therefore need to bump up db version number - // }) + // + // e2e test + it('should be able to set & delete a key->value pair from an indexeddb, and delete a database from indexeddb using these botactions', async() => { + const db1Exists = `(async() => { + const dbs = await window.indexedDB.databases(); + return dbs.map(db => db.name).includes("${databaseName}"); + })()` + const db2Exists = `(async() => { + const dbs = await window.indexedDB.databases(); + return dbs.map(db => db.name).includes("MySecondDatabase"); + })()` + + const browser = await puppeteer.launch(); + const page = await browser.newPage(); + + await page.goto('http://localhost:8080/indexed-db.html') + + // "MyTestDatabase" + const result = await pipe()( + setIndexedDBValue('new-key', 'new-value', storeName, databaseName), + getIndexedDBValue('new-key', storeName, databaseName) + )(page) + const result2 = await pipe()( + getIndexedDBValue(key, storeName, databaseName) + )(page) + + expect(result).toEqual('new-value') + expect(result2).toEqual(value) + const result21 = await page.evaluate(db1Exists, databaseName) + expect(result21).toEqual(true) + + await deleteIndexedDB(databaseName)(page) + const result22 = await page.evaluate(db1Exists, databaseName) + expect(result22).toEqual(false) + + // "MySecondDatabase" + const result3 = await page.evaluate(db2Exists) + expect(result3).toEqual(true) + + await deleteIndexedDB()(page, undefined, 'MySecondDatabase') // simulate indexedDBStore()() HO assembling that injects db name + const result4 = await page.evaluate(db2Exists) + expect(result4).toEqual(false) + + await page.close() + await browser.close() + }) afterAll(() => { jest.unmock('./inject') diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 2ab136fae..779431da9 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -14,9 +14,8 @@ import { deleteIndexedDBDatabase } from '../helpers/indexed-db' * @param page */ export const deleteIndexedDB = (databaseName?: string): BotIndexedDBAction => async(page, ...injects) => { - const [, , injectDatabaseName] = unpipeInjects(injects, 3) + const [, , injectDatabaseName] = unpipeInjects(injects, 2) - // todo e2e test that does not block attempt to delete await page.evaluate( deleteIndexedDBDatabase, databaseName ?? injectDatabaseName diff --git a/libs/core/src/lib/helpers/indexed-db.ts b/libs/core/src/lib/helpers/indexed-db.ts index 252b7091b..0f778be08 100644 --- a/libs/core/src/lib/helpers/indexed-db.ts +++ b/libs/core/src/lib/helpers/indexed-db.ts @@ -8,6 +8,7 @@ import { logError, logMessage } from "./console"; * * @param databaseName */ +/* istanbul ignore next */ export function deleteIndexedDBDatabase(databaseName: string) { return new Promise((resolve, reject) => { const DBDeleteRequest = indexedDB.deleteDatabase(databaseName); @@ -38,6 +39,7 @@ export function deleteIndexedDBDatabase(databaseName: string) { * @param key * @param value */ +/* istanbul ignore next */ export function setIndexedDBStoreValue(databaseName: string, databaseVersion: number, storeName: string, key: string, value: any) { return new Promise((resolve, reject) => { @@ -82,6 +84,7 @@ export function setIndexedDBStoreValue(databaseName: string, databaseVersion: nu * @param storeName * @param key */ +/* istanbul ignore next */ export function getIndexedDBStoreValue(databaseName: string, databaseVersion: number, storeName: string, key: string) { return new Promise((resolve, reject) => { const openRequest = indexedDB.open(databaseName, databaseVersion) From 84af13aba4c5dd44b5c7e64a9f0dd11b0286dc17 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Wed, 24 Mar 2021 23:33:02 -0600 Subject: [PATCH 41/54] house cleaning --- libs/core/src/lib/actions/indexed-db.spec.ts | 5 ++--- libs/core/src/lib/mocks/urls.ts | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/core/src/lib/actions/indexed-db.spec.ts b/libs/core/src/lib/actions/indexed-db.spec.ts index 76002c955..d3acfd766 100644 --- a/libs/core/src/lib/actions/indexed-db.spec.ts +++ b/libs/core/src/lib/actions/indexed-db.spec.ts @@ -9,7 +9,7 @@ import { import * as puppeteer from 'puppeteer' import { pipe } from './assembly-lines' -import { evaluate } from './scrapers' +import { INDEXEDDB_URL } from '../mocks/urls' const mockInject3rdCall = jest.fn() jest.mock('./inject', () => { @@ -42,7 +42,6 @@ describe('[Botmation] actions/indexed-db', () => { // e2e testing const databaseName = 'MyTestDatabase'; - const databaseVersion = undefined; const storeName = 'TestObjectStore'; const key = 'TestKey'; const value = 'TestValue234'; @@ -181,7 +180,7 @@ describe('[Botmation] actions/indexed-db', () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); - await page.goto('http://localhost:8080/indexed-db.html') + await page.goto(INDEXEDDB_URL) // "MyTestDatabase" const result = await pipe()( diff --git a/libs/core/src/lib/mocks/urls.ts b/libs/core/src/lib/mocks/urls.ts index 80c567a9f..37b753b64 100644 --- a/libs/core/src/lib/mocks/urls.ts +++ b/libs/core/src/lib/mocks/urls.ts @@ -4,3 +4,4 @@ export const EXAMPLE_URL = 'http://localhost:8080/example.html' export const EXAMPLE_URL2 = 'http://localhost:8080/example2.html' export const SUCCESS_URL = 'http://localhost:8080/success.html' export const COOKIES_URL = 'http://localhost:8080/cookies.html' +export const INDEXEDDB_URL = 'http://localhost:8080/indexed-db.html' From 8bb5e455a41825b8f6ef95c110725fd7817d7921 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Thu, 25 Mar 2021 00:02:45 -0600 Subject: [PATCH 42/54] code smells --- apps/bot-instagram/src/main.ts | 2 +- libs/core/src/lib/actions/cookies.spec.ts | 13 ++++++------- libs/core/src/lib/actions/cookies.ts | 1 - libs/core/src/lib/actions/indexed-db.spec.ts | 8 +++----- libs/core/src/lib/actions/indexed-db.ts | 3 +-- libs/instagram/src/lib/actions/auth.ts | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index f13302030..2ad886322 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -76,7 +76,7 @@ import { screenshot('logged-in'), - // viewStories, + viewStories, log('screenshot taken'), wait(5000), diff --git a/libs/core/src/lib/actions/cookies.spec.ts b/libs/core/src/lib/actions/cookies.spec.ts index 9e46e1f03..420704f1d 100644 --- a/libs/core/src/lib/actions/cookies.spec.ts +++ b/libs/core/src/lib/actions/cookies.spec.ts @@ -1,4 +1,3 @@ -import { Page, Protocol } from 'puppeteer' import * as puppeteer from 'puppeteer' import { promises as fs, Stats } from 'fs' @@ -39,14 +38,14 @@ describe('[Botmation] actions/cookies', () => { } ] - let mockPage: Page + let mockPage: puppeteer.Page beforeEach(() => { mockPage = { cookies: jest.fn(() => COOKIES_JSON), setCookie: jest.fn(), deleteCookie: jest.fn() - } as any as Page + } as any as puppeteer.Page }) // @@ -95,8 +94,8 @@ describe('[Botmation] actions/cookies', () => { // // deleteCookies it('deleteCookies() should call puppeteer page deleteCookies() method with cookies provided through HO param or fallback pipe value if value is an array', async() => { - const cookies = ['a', 'b', 'c', 'd'] as any as Protocol.Network.Cookie[] - const pipeCookies = ['5', '2', '3', '1'] as any as Protocol.Network.Cookie[] + const cookies = ['a', 'b', 'c', 'd'] as any as puppeteer.Protocol.Network.Cookie[] + const pipeCookies = ['5', '2', '3', '1'] as any as puppeteer.Protocol.Network.Cookie[] await deleteCookies(...cookies)(mockPage) @@ -108,7 +107,7 @@ describe('[Botmation] actions/cookies', () => { expect(mockPage.deleteCookie).toHaveBeenNthCalledWith(2, '5', '2', '3', '1') expect(mockPage.deleteCookie).toHaveBeenCalledTimes(2) - const higherOrderOverwritesPipeValue = ['blue', 'green', 'red'] as any as Protocol.Network.Cookie[] + const higherOrderOverwritesPipeValue = ['blue', 'green', 'red'] as any as puppeteer.Protocol.Network.Cookie[] await deleteCookies(...higherOrderOverwritesPipeValue)(mockPage, wrapValueInPipe(pipeCookies)) @@ -140,7 +139,7 @@ describe('[Botmation] actions/cookies', () => { await pipe()( getCookies(), // todo tap() BotAction? similar to map() BotAction but ignore cb return / auto return pipe value - async(page, pipeObject) => { + async(_, pipeObject) => { expect(pipeObject.value.length).toEqual(2) expect(pipeObject.value[0]).toEqual({"domain": "localhost", "expires": -1, "httpOnly": false, "name": "sessionId", "path": "/", "sameParty": false, "secure": false, "session": true, "size": 16, "sourcePort": 8080, "sourceScheme": "NonSecure", "value": "1235711"}) expect(pipeObject.value[1]).toEqual({"domain": "localhost", "expires": -1, "httpOnly": false, "name": "username", "path": "/", "sameParty": false, "secure": false, "session": true, "size": 16, "sourcePort": 8080, "sourceScheme": "NonSecure", "value": "John Doe"}) diff --git a/libs/core/src/lib/actions/cookies.ts b/libs/core/src/lib/actions/cookies.ts index b79cfb552..ae9adff79 100644 --- a/libs/core/src/lib/actions/cookies.ts +++ b/libs/core/src/lib/actions/cookies.ts @@ -6,7 +6,6 @@ import { BotFileOptions } from '../interfaces' import { getFileUrl } from '../helpers/files' import { getInjectsPipeValue, injectsHavePipe, unpipeInjects } from '../helpers/pipe' import { Protocol } from 'puppeteer' -import { logWarning } from '../helpers/console' /** * @description Parse page's cookies and save them as JSON in a local file diff --git a/libs/core/src/lib/actions/indexed-db.spec.ts b/libs/core/src/lib/actions/indexed-db.spec.ts index d3acfd766..4e7b9affc 100644 --- a/libs/core/src/lib/actions/indexed-db.spec.ts +++ b/libs/core/src/lib/actions/indexed-db.spec.ts @@ -1,5 +1,3 @@ -import { Page } from 'puppeteer' - import { setIndexedDBValue, getIndexedDBValue, @@ -24,7 +22,7 @@ jest.mock('./inject', () => { * @description IndexedDB BotAction's */ describe('[Botmation] actions/indexed-db', () => { - let mockPage: Page + let mockPage: puppeteer.Page // Function sources for the key & value support both higher-order param and Pipe.value const higherOrderParamKey = 'higher-order-key' @@ -49,12 +47,12 @@ describe('[Botmation] actions/indexed-db', () => { beforeEach(() => { mockPage = { evaluate: jest.fn() - } as any as Page + } as any as puppeteer.Page }) it('indexedDBStore()() should set the first few injects as BotIndexedDBInjects from higher order params', async() => { const injectsWithoutPipe = [25, 'hi', 'World'] - mockPage = {} as any as Page + mockPage = {} as any as puppeteer.Page await indexedDBStore(higherOrderDatabaseName, higherOrderStoreName, higherOrderDatabaseVersion)()(mockPage) await indexedDBStore(higherOrderDatabaseName, higherOrderStoreName, higherOrderDatabaseVersion)()(mockPage, ...injectsWithoutPipe) diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 779431da9..9e88fb783 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -1,13 +1,12 @@ import { BotAction, BotIndexedDBAction } from '../interfaces/bot-actions' import { unpipeInjects } from '../helpers/pipe' -import { getIndexedDBStoreValue, setIndexedDBStoreValue } from '../helpers/indexed-db' +import { getIndexedDBStoreValue, setIndexedDBStoreValue, deleteIndexedDBDatabase } from '../helpers/indexed-db' import { inject } from './inject' import { PipeValue } from '../types/pipe-value' import { isObjectWithKey, isObjectWithValue } from '../types/objects' import { getQueryKey, getQueryKeyValue } from '../types/database' import { pipe } from './assembly-lines' -import { deleteIndexedDBDatabase } from '../helpers/indexed-db' /** * diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index 235622dba..1362953e8 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -5,7 +5,7 @@ import { chain, errors } from '@botmation/core' import { goTo, reload, waitForNavigation, wait } from '@botmation/core' import { getCookies, deleteCookies } from '@botmation/core' import { click, type } from '@botmation/core' -import { getIndexedDBValue, indexedDBStore, deleteIndexedDB, clearAllLocalStorage } from '@botmation/core' +import { getIndexedDBValue, indexedDBStore, clearAllLocalStorage } from '@botmation/core' import { map } from '@botmation/core' import { log } from '@botmation/core' From d813f881d2ba4aadbc3279f23df5d3cab6d3f335 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 12 Apr 2021 20:41:25 -0600 Subject: [PATCH 43/54] Nullish Assignment --- libs/core/src/lib/actions/scrapers.ts | 34 +++++---------------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index fd5313190..da7625cbf 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -45,22 +45,11 @@ export const htmlParser = (htmlParserFunction: Function) => * Returns the first Element that matches the provided HTML Selector * @param htmlSelector */ -export const $ = (htmlSelector: string, higherOrderHTMLParser?: Function): ScraperBotAction => +export const $ = (htmlSelector: string, parser?: Function): ScraperBotAction => async(page, ...injects) => { - let parser: Function + let [,injectedHTMLParser] = unpipeInjects(injects, 1) - // Future support piping the HTML selector with higher-order overriding - const [,injectedHTMLParser] = unpipeInjects(injects, 1) - - if (higherOrderHTMLParser) { - parser = higherOrderHTMLParser - } else { - if (injectedHTMLParser) { - parser = injectedHTMLParser - } else { - parser = cheerio.load - } - } + parser ??= injectedHTMLParser ??= cheerio.load const scrapedHTML = await page.evaluate(getElementOuterHTML, htmlSelector) return scrapedHTML ? parser(scrapedHTML) : undefined @@ -70,22 +59,11 @@ export const $ = (htmlSelector: string, higherOrderHTMLParser * Returns an array of parsed HTML Element's as objects (dependent on the html parser used) that match the provided HTML Selector * @param htmlSelector */ -export const $$ = (htmlSelector: string, higherOrderHTMLParser?: Function): ScraperBotAction => +export const $$ = (htmlSelector: string, parser?: Function): ScraperBotAction => async(page, ...injects) => { - let parser: Function - - // Future support piping the HTML selector with higher-order overriding - const [,injectedHTMLParser] = unpipeInjects(injects, 1) + let [,injectedHTMLParser] = unpipeInjects(injects, 1) - if (higherOrderHTMLParser) { - parser = higherOrderHTMLParser - } else { - if (injectedHTMLParser) { - parser = injectedHTMLParser - } else { - parser = cheerio.load - } - } + parser ??= injectedHTMLParser ??= cheerio.load const scrapedHTMLs = await page.evaluate(getElementsOuterHTML, htmlSelector) const cheerioEls: CheerioStatic[] = scrapedHTMLs.map(scrapedHTML => parser(scrapedHTML)) From 48d0cf630317fcffd63d89f26990cdd99a3b73be Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 12 Apr 2021 20:50:25 -0600 Subject: [PATCH 44/54] Nullish Assignment --- libs/core/src/lib/actions/local-storage.ts | 33 ++++------------------ libs/core/src/lib/actions/random.ts | 16 ++++------- 2 files changed, 11 insertions(+), 38 deletions(-) diff --git a/libs/core/src/lib/actions/local-storage.ts b/libs/core/src/lib/actions/local-storage.ts index 956a32d27..d62fe7079 100644 --- a/libs/core/src/lib/actions/local-storage.ts +++ b/libs/core/src/lib/actions/local-storage.ts @@ -56,27 +56,14 @@ export const setLocalStorageItem = async(page, ...injects) => { const pipeValue = getInjectsPipeValue(injects) - if (!value) { - if (pipeValue) { - // idea here is that the Pipe value is anothe object with keys {key: '', value: ''} -> to map as what we are setting in the DB - if (pipeValue.value) { - value = pipeValue.value - } else { - // with potential fallback that the Pipe's value IS the value to set, and we'll get the key from the BotAction's higher order `key` param - value = pipeValue - } - } - } - if (!key) { - if (pipeValue && pipeValue.key) { - key = pipeValue.key - } - } + // todo throw an error , don't error silently + value ??= pipeValue ? pipeValue.value ? pipeValue.value : pipeValue : 'missing-value' + key ??= pipeValue && pipeValue.key ? pipeValue.key : 'missing-key' await page.evaluate( setLocalStorageKeyValue, - key ?? 'missing-key', - value ?? 'missing-value' + key, + value ) } @@ -91,15 +78,7 @@ export const getLocalStorageItem = async(page, ...injects) => { const pipeValue = getInjectsPipeValue(injects) - if (!key) { - if (pipeValue) { - if (pipeValue.key) { - key = pipeValue.key - } else { - key = pipeValue - } - } - } + key ??= pipeValue && pipeValue.key ? pipeValue.key : pipeValue return page.evaluate( getLocalStorageKeyValue, diff --git a/libs/core/src/lib/actions/random.ts b/libs/core/src/lib/actions/random.ts index 31c21b980..8407b004a 100644 --- a/libs/core/src/lib/actions/random.ts +++ b/libs/core/src/lib/actions/random.ts @@ -34,23 +34,17 @@ export const rollDice = * ie if probability is 60% then all numbers generated 0-60 will cause actions to run and all numbers generated 61-100 will cause nothing * @future use givenThat() with a wrapper function post v2 (support sync BotActions and decomposed `page` from params into an inject) * @param probability represented as a decimal ie .6 = 60% chance of running assembled BotActions - * @param overloadGenerateRandomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method + * @param randomDecimal function to generate a random decimal between 0 and 1 with default set to randomDecimal helper that uses a pseudo random method */ export const probably = - (probability = .6, overloadGenerateRandomDecimal?: NumberReturningFunc) => + (probability = .6, randomDecimal?: NumberReturningFunc) => (...actions: BotAction[]): BotAction => async(page, ...injects) => { - if (!overloadGenerateRandomDecimal) { - const [,injectedRandomDecimalFunction] = unpipeInjects(injects, 1) + let [,injectedRandomDecimalFunction] = unpipeInjects(injects, 1) - if (typeof injectedRandomDecimalFunction === 'function') { - overloadGenerateRandomDecimal = injectedRandomDecimalFunction // once injects becomes Map based :) - } else { - overloadGenerateRandomDecimal = generateRandomDecimal - } - } + randomDecimal ??= injectedRandomDecimalFunction ??= generateRandomDecimal - if (overloadGenerateRandomDecimal() <= probability) { + if (randomDecimal() <= probability) { return assemblyLine()(...actions)(page, ...injects) } } From 6d0efe8d6e4cd4c650e5493439b01ed8db839e1e Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 12 Apr 2021 21:07:44 -0600 Subject: [PATCH 45/54] Nullish Assignment --- libs/core/src/lib/actions/indexed-db.ts | 36 +++++----------------- libs/core/src/lib/actions/local-storage.ts | 10 +----- libs/core/src/lib/types/objects.ts | 14 ++++----- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 9e88fb783..00ed300e1 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -7,6 +7,7 @@ import { PipeValue } from '../types/pipe-value' import { isObjectWithKey, isObjectWithValue } from '../types/objects' import { getQueryKey, getQueryKeyValue } from '../types/database' import { pipe } from './assembly-lines' +import { pipeValue } from './pipe' /** * @@ -51,31 +52,18 @@ export const indexedDBStore = (databaseName: string, storeName: string, database export const setIndexedDBValue = (key?: string, value?: any, storeName?: string, databaseName?: string, databaseVersion?: number): BotIndexedDBAction => async(page, ...injects) => { - const [pipedValue, injectDatabaseVersion, injectDatabaseName, injectStoreName] = unpipeInjects(injects, 3) + let [pipeValue, injectDatabaseVersion, injectDatabaseName, injectStoreName] = unpipeInjects(injects, 3) - if (!value) { - if (pipedValue) { - // idea here is that the piped value is another object with keys {key: '', value: ''} -> to map as what we are setting in the DB - if (isObjectWithValue(pipedValue)) { - value = pipedValue.value - } else { - value = pipedValue - } - } - } - if (!key) { - if (isObjectWithKey(pipedValue)) { - key = pipedValue.key - } - } + value ??= isObjectWithValue(pipeValue) ? pipeValue.value : pipeValue ??= 'missing-value' + key ??= isObjectWithKey(pipeValue) ? pipeValue.key : 'missing-key' await page.evaluate( setIndexedDBStoreValue, databaseName ? databaseName : injectDatabaseName ?? 'missing-db-name', databaseVersion ? databaseVersion : injectDatabaseVersion ?? undefined, // grab latest version storeName ? storeName : injectStoreName ?? 'missing-store', - key ?? 'missing-key', - value ?? 'missing-value' + key, + value ) } @@ -91,17 +79,9 @@ export const setIndexedDBValue = export const getIndexedDBValue = (key?: string, storeName?: string, databaseName?: string, databaseVersion?: number): BotIndexedDBAction => async(page, ...injects) => { - const [pipeValue, injectDatabaseVersion, injectDatabaseName, injectStoreName] = unpipeInjects(injects, 3) + let [pipeValue, injectDatabaseVersion, injectDatabaseName, injectStoreName] = unpipeInjects(injects, 3) - if (!key) { - if (pipeValue) { - if (isObjectWithKey(pipeValue)) { - key = pipeValue.key - } else { - key = pipeValue - } - } - } + key ??= isObjectWithKey(pipeValue) ? pipeValue.key : (pipeValue as string) ??= 'missing-key' return page.evaluate( getIndexedDBStoreValue, diff --git a/libs/core/src/lib/actions/local-storage.ts b/libs/core/src/lib/actions/local-storage.ts index d62fe7079..5798c456a 100644 --- a/libs/core/src/lib/actions/local-storage.ts +++ b/libs/core/src/lib/actions/local-storage.ts @@ -28,15 +28,7 @@ export const removeLocalStorageItem = async(page, ...injects) => { const pipeValue = getInjectsPipeValue(injects) - if (!key) { - if (pipeValue) { - if (pipeValue.key) { - key = pipeValue.key - } else { - key = pipeValue - } - } - } + key ??= pipeValue && pipeValue.key ? pipeValue.key : pipeValue await page.evaluate( removeLocalStorageKeyValue, diff --git a/libs/core/src/lib/types/objects.ts b/libs/core/src/lib/types/objects.ts index b65407646..5b05ff2a6 100644 --- a/libs/core/src/lib/types/objects.ts +++ b/libs/core/src/lib/types/objects.ts @@ -1,12 +1,12 @@ /** - * Generic Object Typing to Help + * Generic Object Typing to Help * Dictionaries, key/value pairs, or just a key */ /** * @description Is data provided an Object with a `key` property that holds a `string` value * ie getting data in a DB - * @param data + * @param data */ export const isObjectWithKey = (data: any): data is {key: string} => { if (!data) { @@ -18,7 +18,7 @@ export const isObjectWithKey = (data: any): data is {key: string} => { /** * @description Is data provided an Object with a `value` property - * @param data + * @param data */ export const isObjectWithValue = (data: any): data is {value: any} => { if (!data) { @@ -32,17 +32,17 @@ export const isObjectWithValue = (data: any): data is {value: any} => { * Basic Hash-Map type */ export type Dictionary = { - [key: string]: V + [key: string]: V } /** * Dictionaries are non-null objects with at least one key/value - * @param value + * @param value */ -export const isDictionary = (value: any): value is Dictionary => +export const isDictionary = (value: any): value is Dictionary => typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).every(key => typeof key === 'string') /** * Array or Dictionary */ -export type Collection = Array|Dictionary \ No newline at end of file +export type Collection = Array|Dictionary From ac8f43823cc6a7ee73a43f1303f87ac546483a8a Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Fri, 16 Apr 2021 10:13:25 -0600 Subject: [PATCH 46/54] code smell --- libs/core/src/lib/actions/indexed-db.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 00ed300e1..5425af7c2 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -7,7 +7,6 @@ import { PipeValue } from '../types/pipe-value' import { isObjectWithKey, isObjectWithValue } from '../types/objects' import { getQueryKey, getQueryKeyValue } from '../types/database' import { pipe } from './assembly-lines' -import { pipeValue } from './pipe' /** * From 84b4d242b4de2bb2da926566630dbfbb801bbc36 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Mon, 19 Apr 2021 19:56:12 -0600 Subject: [PATCH 47/54] simplify goTo implementation --- libs/core/src/lib/actions/navigation.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libs/core/src/lib/actions/navigation.ts b/libs/core/src/lib/actions/navigation.ts index 803ae0250..938a62a3b 100644 --- a/libs/core/src/lib/actions/navigation.ts +++ b/libs/core/src/lib/actions/navigation.ts @@ -14,14 +14,11 @@ import { wait } from './time' */ export const goTo = (url: string, goToOptions?: Partial): BotAction => async(page) => { - goToOptions = enrichGoToPageOptions(goToOptions) - - // same url check if (page.url() === url) { return } - await page.goto(url, goToOptions) + await page.goto(url, enrichGoToPageOptions(goToOptions)) } /** From 5d7ffcfed840e9aa7d4e758b376396a438c20c31 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 11:34:39 -0600 Subject: [PATCH 48/54] tests + code polish --- libs/core/src/lib/actions/cookies.ts | 1 + libs/core/src/lib/actions/indexed-db.ts | 2 +- libs/core/src/lib/actions/scrapers.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/core/src/lib/actions/cookies.ts b/libs/core/src/lib/actions/cookies.ts index ae9adff79..659e38c87 100644 --- a/libs/core/src/lib/actions/cookies.ts +++ b/libs/core/src/lib/actions/cookies.ts @@ -15,6 +15,7 @@ import { Protocol } from 'puppeteer' * @param fileName * @example saveCookies('cookies') -> creates `cookies.json` */ +// TODO spike saveCookies concept using pipe(getCookies, saveToDisk) export const saveCookies = (fileName: string, botFileOptions?: Partial): BotFilesAction => async(page, ...injects) => { const [,injectedOptions] = unpipeInjects(injects, 1) diff --git a/libs/core/src/lib/actions/indexed-db.ts b/libs/core/src/lib/actions/indexed-db.ts index 5425af7c2..e8db90e74 100644 --- a/libs/core/src/lib/actions/indexed-db.ts +++ b/libs/core/src/lib/actions/indexed-db.ts @@ -80,7 +80,7 @@ export const getIndexedDBValue = async(page, ...injects) => { let [pipeValue, injectDatabaseVersion, injectDatabaseName, injectStoreName] = unpipeInjects(injects, 3) - key ??= isObjectWithKey(pipeValue) ? pipeValue.key : (pipeValue as string) ??= 'missing-key' + key ??= isObjectWithKey(pipeValue) ? pipeValue.key : pipeValue as string return page.evaluate( getIndexedDBStoreValue, diff --git a/libs/core/src/lib/actions/scrapers.ts b/libs/core/src/lib/actions/scrapers.ts index da7625cbf..d09693d45 100644 --- a/libs/core/src/lib/actions/scrapers.ts +++ b/libs/core/src/lib/actions/scrapers.ts @@ -19,14 +19,14 @@ import { pipe } from './assembly-lines' * @param elementSelector */ export const elementExists = (elementSelector: string): ConditionalBotAction => -evaluate(elementExistsInDocument, elementSelector) + evaluate(elementExistsInDocument, elementSelector) /** * * @param text */ export const textExists = (text: string): ConditionalBotAction => -evaluate(textExistsInDocument, text) + evaluate(textExistsInDocument, text) /** * @description Inject htmlParser for ScraperBotAction's From 6ea0cb30e4477325a1f620175f81b743cd5afe0a Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 13:07:09 -0600 Subject: [PATCH 49/54] 17 botaction groups - added random --- README.md | 4 +++- libs/core/README.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 463153878..a69cacb0a 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Import any core API function from: import { chain, goTo, screenshot } from '@botmation/core' ``` -`@botmation/core` v1 has 16 groups of BotActions to choose from: +`@botmation/core` v1 has 17 groups of BotActions to choose from: Leader Bot @@ -98,6 +98,8 @@ import { chain, goTo, screenshot } from '@botmation/core' - change the page's URL, wait for form submissions to change page URL, back, forward, refresh - [pipe](https://www.botmation.dev/api/pipe) - functions specific to Piping + - [random](https://www.botmation.dev/api/random) + - functions specific to randomness like rolling dice - [scrapers](https://www.botmation.dev/api/scrapers) - scrape HTML documents with an HTML parser and evaluate JavaScript inside a Page - [time](https://www.botmation.dev/api/time) diff --git a/libs/core/README.md b/libs/core/README.md index ee5e8f344..9402f890b 100644 --- a/libs/core/README.md +++ b/libs/core/README.md @@ -72,7 +72,7 @@ Import any core API function from: import { chain, goTo, screenshot } from '@botmation/core' ``` -`@botmation/core` v1 has 16 groups of BotActions to choose from: +`@botmation/core` v1 has 17 groups of BotActions to choose from: Leader Bot @@ -104,6 +104,8 @@ import { chain, goTo, screenshot } from '@botmation/core' - change the page's URL, wait for form submissions to change page URL, back, forward, refresh - [pipe](https://www.botmation.dev/api/pipe) - functions specific to Piping + - [random](https://www.botmation.dev/api/random) + - functions specific to randomness like rolling dice - [scrapers](https://www.botmation.dev/api/scrapers) - scrape HTML documents with an HTML parser and evaluate JavaScript inside a Page - [time](https://www.botmation.dev/api/time) From c6c48f8b28a127922cbc0552b9285c95f8fc7ebe Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 17:48:34 -0600 Subject: [PATCH 50/54] consolidated URL helpers into simpler constants --- libs/instagram/src/lib/actions/auth.ts | 6 +++--- libs/instagram/src/lib/actions/modals.ts | 2 +- libs/instagram/src/lib/actions/navigation.ts | 13 +++++-------- libs/instagram/src/lib/actions/stories.ts | 2 +- libs/instagram/src/lib/{ => constants}/selectors.ts | 0 libs/instagram/src/lib/constants/urls.ts | 8 ++++++-- libs/instagram/src/lib/helpers/urls.ts | 13 ------------- 7 files changed, 16 insertions(+), 28 deletions(-) rename libs/instagram/src/lib/{ => constants}/selectors.ts (100%) delete mode 100644 libs/instagram/src/lib/helpers/urls.ts diff --git a/libs/instagram/src/lib/actions/auth.ts b/libs/instagram/src/lib/actions/auth.ts index 1362953e8..a2e20dc31 100644 --- a/libs/instagram/src/lib/actions/auth.ts +++ b/libs/instagram/src/lib/actions/auth.ts @@ -9,12 +9,12 @@ import { getIndexedDBValue, indexedDBStore, clearAllLocalStorage } from '@botmat import { map } from '@botmation/core' import { log } from '@botmation/core' -import { getInstagramLoginUrl } from '../helpers/urls' +import { INSTAGRAM_URL_LOGIN } from '../constants/urls' import { FORM_AUTH_USERNAME_INPUT_SELECTOR, FORM_AUTH_PASSWORD_INPUT_SELECTOR, FORM_AUTH_SUBMIT_BUTTON_SELECTOR -} from '../selectors' +} from '../constants/selectors' /** * @description ConditionalBotAction that resolves TRUE if the User is NOT logged in @@ -47,7 +47,7 @@ export const isLoggedIn: ConditionalBotAction = export const login = ({username, password}: {username: string, password: string}): BotAction => chain( errors('Instagram login()')( - goTo(getInstagramLoginUrl()), + goTo(INSTAGRAM_URL_LOGIN), click(FORM_AUTH_USERNAME_INPUT_SELECTOR), type(username), click(FORM_AUTH_PASSWORD_INPUT_SELECTOR), diff --git a/libs/instagram/src/lib/actions/modals.ts b/libs/instagram/src/lib/actions/modals.ts index de4bd92d7..427b01e99 100644 --- a/libs/instagram/src/lib/actions/modals.ts +++ b/libs/instagram/src/lib/actions/modals.ts @@ -4,7 +4,7 @@ import { BotAction } from '@botmation/core' import { TURN_OFF_NOTIFICATIONS_BUTTON_LABEL } from '../constants/modals' -import { MAIN_MODAL_HEADER_SELECTOR } from '../selectors' +import { MAIN_MODAL_HEADER_SELECTOR } from '../constants/selectors' import { TURN_OFF_NOTIFICATIONS_MODAL_HEADER_TEXT } from '../constants/modals' import { ConditionalBotAction } from '@botmation/core' diff --git a/libs/instagram/src/lib/actions/navigation.ts b/libs/instagram/src/lib/actions/navigation.ts index 7eacfe0fa..a3f61ddfc 100644 --- a/libs/instagram/src/lib/actions/navigation.ts +++ b/libs/instagram/src/lib/actions/navigation.ts @@ -1,10 +1,7 @@ import { BotAction, goTo } from "@botmation/core" +import { INSTAGRAM_URL_BASE, INSTAGRAM_URL_EXPLORE, INSTAGRAM_URL_MESSAGING, INSTAGRAM_URL_SETTINGS } from "../constants/urls" - -export const goToHome: BotAction = goTo('https://www.instagram.com/') - -export const goToMessaging: BotAction = goTo('https://www.instagram.com/direct/inbox/') - -export const goToExplore: BotAction = goTo('https://www.instagram.com/explore/') - -export const goToSettings: BotAction = goTo('https://www.instagram.com/accounts/edit/') +export const goToHome: BotAction = goTo(INSTAGRAM_URL_BASE) +export const goToMessaging: BotAction = goTo(INSTAGRAM_URL_MESSAGING) +export const goToExplore: BotAction = goTo(INSTAGRAM_URL_EXPLORE) +export const goToSettings: BotAction = goTo(INSTAGRAM_URL_SETTINGS) diff --git a/libs/instagram/src/lib/actions/stories.ts b/libs/instagram/src/lib/actions/stories.ts index d2f930f07..3111617bb 100644 --- a/libs/instagram/src/lib/actions/stories.ts +++ b/libs/instagram/src/lib/actions/stories.ts @@ -3,7 +3,7 @@ */ import { BotAction, click, forAsLong, wait, elementExists, givenThat } from "@botmation/core"; -import { FIRST_STORY, STORIES_VIEWER_NEXT_STORY_ICON } from "../selectors"; +import { FIRST_STORY, STORIES_VIEWER_NEXT_STORY_ICON } from "../constants/selectors"; /** * When on the Home page, run this BotAction to cause the bot to open the Stories Theater Mode with the 1st Story then view them all until there are no more left diff --git a/libs/instagram/src/lib/selectors.ts b/libs/instagram/src/lib/constants/selectors.ts similarity index 100% rename from libs/instagram/src/lib/selectors.ts rename to libs/instagram/src/lib/constants/selectors.ts diff --git a/libs/instagram/src/lib/constants/urls.ts b/libs/instagram/src/lib/constants/urls.ts index 17414dd14..aa47f67d3 100644 --- a/libs/instagram/src/lib/constants/urls.ts +++ b/libs/instagram/src/lib/constants/urls.ts @@ -2,5 +2,9 @@ * @description Crawled URL's belonging to Instagram * Broken apart into BASE & EXT (URL extensions) */ -export const INSTAGRAM_BASE_URL = 'https://www.instagram.com' -export const INSTAGRAM_URL_EXT_LOGIN = '/accounts/login' \ No newline at end of file +export const INSTAGRAM_URL_BASE = 'https://www.instagram.com/' + +export const INSTAGRAM_URL_LOGIN = INSTAGRAM_URL_BASE + 'accounts/login/' +export const INSTAGRAM_URL_MESSAGING = INSTAGRAM_URL_BASE + 'direct/inbox/' +export const INSTAGRAM_URL_EXPLORE = INSTAGRAM_URL_BASE + 'explore/' +export const INSTAGRAM_URL_SETTINGS = INSTAGRAM_URL_BASE + 'accounts/edit/' diff --git a/libs/instagram/src/lib/helpers/urls.ts b/libs/instagram/src/lib/helpers/urls.ts deleted file mode 100644 index dad830836..000000000 --- a/libs/instagram/src/lib/helpers/urls.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { INSTAGRAM_URL_EXT_LOGIN, INSTAGRAM_BASE_URL } from "../constants/urls" - -/** - * @description Returns the main base URL for Instagram without a trailing backslash - */ -export const getInstagramBaseUrl = () => - INSTAGRAM_BASE_URL - -/** - * @description Returns Instagram's Login URL with a trailing backslash - */ -export const getInstagramLoginUrl = () => - getInstagramBaseUrl() + INSTAGRAM_URL_EXT_LOGIN + '/' \ No newline at end of file From 1bb45e3887977ad8eec5c62017bbff66830d3748 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 18:32:24 -0600 Subject: [PATCH 51/54] fix instagram barrel + desktop widths --- apps/bot-instagram/src/main.ts | 39 ++++++++++++++++++++++++++++------ libs/instagram/src/index.ts | 5 +---- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 2ad886322..42e0c0a65 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -23,7 +23,10 @@ import { viewStories, isSaveYourLoginInfoActive, clickSaveYourLoginInfoNoButton, - logout + logout, + goToExplore, + goToMessaging, + goToSettings } from '@botmation/instagram' (async () => { @@ -34,6 +37,15 @@ import { const pages = await browser.pages() const page = pages.length === 0 ? await browser.newPage() : pages[0] + // Instagram BotActions were developed for the UI responsiveness in desktop widths + // specifically, in mobile widths, the `viewStories` BotAction will open your story + // instead of the presentation of stories + await page.setViewport({ + width: 1000, + height: 600, + deviceScaleFactor: 1, + }); + await chain( log('Botmation running'), @@ -54,10 +66,11 @@ import { // lets log in, if we are a guest givenThat(isGuest) ( - login({username: 'account', password: 'password'}), // <- put your username and password here + login({username: 'lagmahol', password: 'jesu1t2007!'}), // <- put your username and password here files()( saveCookies('instagram'), // the Bot will skip login, on next run, by loading cookies - ) + ), + ), // in case that log in failed, lets check before we operate as a logged in user @@ -76,18 +89,32 @@ import { screenshot('logged-in'), - viewStories, + // viewStories, log('screenshot taken'), + wait(12000), + + goToExplore, + + wait(2000), + + goToMessaging, + + wait(2000), + + goToSettings, + + wait(5000), - logout, + // logout, - log('logout complete'), + // log('logout complete'), wait(15000) ), + log('Done'), )(page) diff --git a/libs/instagram/src/index.ts b/libs/instagram/src/index.ts index 081713e27..9ade00506 100644 --- a/libs/instagram/src/index.ts +++ b/libs/instagram/src/index.ts @@ -5,7 +5,4 @@ export * from './lib/actions/stories' export * from './lib/constants/modals' export * from './lib/constants/urls' - -export * from './lib/helpers/urls' - -export * from './lib/selectors' +export * from './lib/constants/selectors' From 5cd99fcf2635ec5c95840df7842a90a5241736a7 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 18:50:30 -0600 Subject: [PATCH 52/54] fix --- apps/bot-instagram/src/main.ts | 31 ++++--------------------------- 1 file changed, 4 insertions(+), 27 deletions(-) diff --git a/apps/bot-instagram/src/main.ts b/apps/bot-instagram/src/main.ts index 42e0c0a65..bc8da64fc 100644 --- a/apps/bot-instagram/src/main.ts +++ b/apps/bot-instagram/src/main.ts @@ -23,10 +23,6 @@ import { viewStories, isSaveYourLoginInfoActive, clickSaveYourLoginInfoNoButton, - logout, - goToExplore, - goToMessaging, - goToSettings } from '@botmation/instagram' (async () => { @@ -66,11 +62,10 @@ import { // lets log in, if we are a guest givenThat(isGuest) ( - login({username: 'lagmahol', password: 'jesu1t2007!'}), // <- put your username and password here + login({username: 'account', password: 'password'}), // <- put your username and password here files()( - saveCookies('instagram'), // the Bot will skip login, on next run, by loading cookies + saveCookies('instagram'), // the Bot will skip login, on next run, by loading the cookies from this file ), - ), // in case that log in failed, lets check before we operate as a logged in user @@ -89,32 +84,14 @@ import { screenshot('logged-in'), - // viewStories, - log('screenshot taken'), - - wait(12000), - - goToExplore, - - wait(2000), - - goToMessaging, - - wait(2000), - - goToSettings, + viewStories, + log('screenshot taken'), wait(5000), - // logout, - - // log('logout complete'), - - wait(15000) ), - log('Done'), )(page) From 9804fee930baaa768a996f80588ab0d7a618f84b Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 19:01:31 -0600 Subject: [PATCH 53/54] new minor versions for core and instagram pckgs --- CHANGELOG.md | 21 +++++++++++++++++++++ libs/core/package.json | 2 +- libs/instagram/package.json | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84ff0ee7c..e64a5ef91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## v1.3.0 @botmation/core + +New BotActions group called "Random" for running BotActions on a probability. + +Couple bug fixes. + +New auxiliary Instagram package v1.1.0: + - navigation + - view stories + - logout + +More details [here](https://github.com/mrWh1te/Botmation/pull/105). + +## v1.2.0 @botmation/core + +New BotAction called [restart](https://www.botmation.dev/api/abort#restart) for restarting an assembled lines of BotActions that aborts. + +Added support for latest major Puppeteer v8. + +More details [here](https://github.com/mrWh1te/Botmation/pull/97). + ## v1.1.0 @botmation/core New `schedule()` BotAction, reorganization of BotAction groups (Utilities => Time, Loops, Branching) and minor performance improvement of a few core BotActions. diff --git a/libs/core/package.json b/libs/core/package.json index 2f4e24add..d9ba85180 100644 --- a/libs/core/package.json +++ b/libs/core/package.json @@ -1,6 +1,6 @@ { "name": "@botmation/core", - "version": "1.2.1", + "version": "1.3.0", "description": "Main package of functions for the TypeScript framework Botmation", "homepage": "https://botmation.dev", "author": "Michael Lage", diff --git a/libs/instagram/package.json b/libs/instagram/package.json index b9cb94978..e70a5aac8 100644 --- a/libs/instagram/package.json +++ b/libs/instagram/package.json @@ -1,6 +1,6 @@ { "name": "@botmation/instagram", - "version": "1.0.2", + "version": "1.1.0", "description": "Auxiliary package of functions for the TypeScript framework Botmation", "homepage": "https://www.botmation.dev/sites/instagram", "author": "Michael Lage", From 6f8f49f66f04a483de4504d49e6ff00392b562f9 Mon Sep 17 00:00:00 2001 From: Michael Lage Date: Tue, 20 Apr 2021 19:12:58 -0600 Subject: [PATCH 54/54] instagram reference latest core pckg --- libs/instagram/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/instagram/package.json b/libs/instagram/package.json index e70a5aac8..91108c318 100644 --- a/libs/instagram/package.json +++ b/libs/instagram/package.json @@ -23,7 +23,7 @@ "url": "https://github.com/mrWh1te/Botmation.git" }, "peerDependencies": { - "@botmation/core": "^1.2.0" + "@botmation/core": "^1.3.0" }, "dependencies": { "tslib": "^2.1.0"