Skip to content

Commit

Permalink
Merge pull request #9 from mrWh1te/final-checkup
Browse files Browse the repository at this point in the history
Pre-flight checkup
  • Loading branch information
mrWh1te authored Feb 18, 2020
2 parents 0a7cfe3 + 2a385cf commit 3c9fa57
Show file tree
Hide file tree
Showing 22 changed files with 208 additions and 7 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
[![dependencies Status](https://david-dm.org/mrWh1te/Botmation/status.svg)](https://david-dm.org/mrWh1te/Botmation)
![GitHub](https://img.shields.io/github/license/mrWh1te/Botmation)

<img src="https://raw.githubusercontent.com/mrWh1te/Botmation/master/assets/art/group.jpg" alt="Botmation" width="400">
<img src="https://raw.githubusercontent.com/mrWh1te/Botmation/master/assets/art/banner/1556x379v2.png" alt="Botmation Crew" width="474">

A TypeScript library for using [Puppeteer](https://github.com/puppeteer/puppeteer) in a declarative way.

The name is a mix of Bot & Automation

Why choose Botmation?
---------------------

Expand Down
Binary file added assets/art/baby_bot.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/banner/1556x379.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/banner/1556x379v2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/banner/900x172.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/blue_bot.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/art/bot-baby.png
Binary file not shown.
Binary file removed assets/art/bot-blue.png
Binary file not shown.
Binary file removed assets/art/bot-orange.png
Binary file not shown.
Binary file removed assets/art/bot-red.png
Binary file not shown.
Binary file removed assets/art/bot-yellow.png
Binary file not shown.
Binary file added assets/art/orange_bot.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/red_bot.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/art/social.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/social2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/art/yellow_bot.PNG
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/botmation/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export class Botmation implements BotmationInterface {
/**
* @description Public method to set the Injects if needed
* @param injects
* @experimental Injects are new
*/
public setInjects(...injects: any[]) {
this.injects = injects
Expand All @@ -122,7 +123,6 @@ export class Botmation implements BotmationInterface {
async closePage(): Promise<void> {
if (this.page) {
await this.page.close()
console.log('') // add an empty line too console for separation upon next
}
}
}
2 changes: 1 addition & 1 deletion src/botmation/helpers/bot-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BotOptions } from "../interfaces/bot-options.interfaces"
* ie screenshot(), saveCookies() rely on BotOptions for determining URL
* @param options
*/
export const getDefaultBotOptions = (options: Partial<BotOptions>): BotOptions => ({
export const getDefaultBotOptions = (options: Partial<BotOptions> = {}): BotOptions => ({
screenshots_directory: '',
cookies_directory: '',
...options
Expand Down
2 changes: 1 addition & 1 deletion src/botmation/helpers/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
* @description Delay execution of next line by X milliseconds, promisified so you can await a setTimeout call
* @param milliseconds
*/
export const sleep = async(milliseconds: number): Promise<any> =>
export const sleep = async(milliseconds: number): Promise<any> => // TODO: confirm 'async' is necessary
new Promise(resolve => setTimeout(resolve, milliseconds))
151 changes: 151 additions & 0 deletions src/tests/botmation/botmation.class.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Page } from 'puppeteer'

import { getDefaultGoToPageOptions } from 'botmation/helpers/navigation'
import { click, type } from 'botmation/actions/input'
import { goTo } from 'botmation/actions/navigation'
import { Botmation } from 'botmation/class'
import { BotActionsChainFactory } from 'botmation/factories/bot-actions-chain.factory'

import { BASE_URL, EXAMPLE_URL, EXAMPLE_URL2 } from '../urls'
import { FORM_TEXT_INPUT_SELECTOR, FORM_SUBMIT_BUTTON_SELECTOR } from '../selectors'
import { BotOptions } from 'botmation/interfaces'
import { getDefaultBotOptions } from 'botmation/helpers/bot-options'

/**
* @description Test the Botmation class methods specific to the Class
*/
describe('[Botmation] Class Specific', () => {

beforeEach(async() => {
await page.goto(BASE_URL, getDefaultGoToPageOptions())
})

//
// set page
it('should have a mutator method for changing the page instance', async() => {
// Create bot with actual page
const bot = new Botmation(page)

// Change bot's page with mock page
const mockPage = {
click: jest.fn()
} as any as Page

// what we are testing:
bot.setPage(mockPage)

// do these actions run on the mock page or the initial page?
await bot.actions(
click('mock selector')
)

expect(mockPage.click).toHaveBeenNthCalledWith(1, 'mock selector')
})

//
// set options
it('should have a mutator method for setting the options injected into the bot actions (also verifies default bot option values)', async() => {
const mockPage = {
url: jest.fn(() => ''),
goto: jest.fn()
} as any as Page
const bot = new Botmation(mockPage) // has default options

const botOptionsOverload: Partial<BotOptions> = {
cookies_directory: 'cookies_test_directory',
screenshots_directory: 'screenshots_test_directory',
parent_output_directory: 'parent_test_directory'
}

// what we are testing
bot.setOptions(botOptionsOverload)

// Let's double-check the default values of bot options
let botOptions = getDefaultBotOptions()
expect(botOptions.parent_output_directory).toEqual(undefined)
expect(botOptions.cookies_directory).toEqual('')
expect(botOptions.screenshots_directory).toEqual('')

await bot.actions(
(page: Page, options: BotOptions) => new Promise<void>(resolve => {
botOptions = options
return resolve()
})
)

// now let's see if setOptions changed the injected options
expect(botOptionsOverload.parent_output_directory).toEqual('parent_test_directory')
expect(botOptions.cookies_directory).toEqual('cookies_test_directory')
expect(botOptions.screenshots_directory).toEqual('screenshots_test_directory')
})

//
// update options
it('should have a mutator method for overloading pieces of the options injected into the bot actions', async() => {
const mockPage = {
url: jest.fn(() => ''),
goto: jest.fn()
} as any as Page
const bot = new Botmation(mockPage) // has default options

const botOptionsOverload: Partial<BotOptions> = {
parent_output_directory: 'parent_test_directory'
}

bot.updateOptions(botOptionsOverload) // has now parent_output_directory

let newParentOutputDirectoryValue = undefined

await bot.actions(
(page: Page, options: BotOptions) => new Promise<void>(resolve => {
newParentOutputDirectoryValue = options.parent_output_directory
return resolve()
})
)

expect(newParentOutputDirectoryValue).toEqual('parent_test_directory')
})

//
// set injects
it('should have a mutator method for setting the injects (optional stuff for devs to add for custom bot actions) that are injected into the bot actions', async() => {
const mockPage = {} as any as Page
const inject1 = jest.fn()

const bot = new Botmation(mockPage)
bot.setInjects(inject1)

inject1()
expect(inject1).toHaveBeenCalled()

await bot.actions(
(page: Page, options: BotOptions, ...injects: any[]) => new Promise<void>(resolve => {
injects[0]()
return resolve()
})
)

expect(inject1).toHaveBeenNthCalledWith(2)
})

//
// close page
it('should have a method to close the page the bot is crawling on, which is the tab in the browser', async() => {
const mockPage = {
close: jest.fn()
} as any as Page

const bot = new Botmation(mockPage)

// what we are testing
bot.closePage()

expect(mockPage.close).toHaveBeenCalled()

// we only run close if the page is defined
bot.setPage(undefined as any as Page)
bot.closePage()

expect(mockPage.close).not.toHaveBeenNthCalledWith(2)
})
})
25 changes: 24 additions & 1 deletion src/tests/botmation/botmation.wrappers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page, Browser } from 'puppeteer'

import { getDefaultGoToPageOptions } from 'botmation/helpers/navigation'
import { click, type } from 'botmation/actions/input'
Expand Down Expand Up @@ -49,6 +49,29 @@ describe('[Botmation:Wrappers] Class & Factory', () => {
const page = bot.getPage()
expect(page.url()).toEqual('http://localhost:8080/success.html?answer=loremlipsumloremlipsum')
})
it('should create a Botmation instance using the static asyncConstructor() and create a new page when the browser has none automatically', async() => {
/// this mocked use-case is for when the browser provided has no tabs open
const mockPage = {
click: jest.fn()
} as any as Page
const mockPages = [] as any // no pages
// the async constructor method's purpose is to get the page (tab) from the browser
// so if none, it needs to create it, then use it
const mockBrowser = {
pages: jest.fn(() => mockPages),
newPage: jest.fn(() => mockPage)
} as any as Browser

const bot = await Botmation.asyncConstructor(mockBrowser)

await bot.actions(
click('example html selector')
)

expect(mockBrowser.pages).toHaveBeenCalled()
expect(mockBrowser.newPage).toHaveBeenCalled()
expect(mockPage.click).toHaveBeenCalledWith('example html selector')
})

//
// Functional using BotActionsChainFactory
Expand Down
29 changes: 29 additions & 0 deletions src/tests/botmation/helpers/utilities.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { sleep } from 'botmation/helpers/utilities'

/**
* @description Helpers: Utilities
*/
describe('[Botmation:Helpers] Utilities', () => {
let setTimeoutFn = setTimeout

afterAll(() => {
// the test mocks the global setTimeout, so restore
// it to original value after the test completes
global.setTimeout = setTimeoutFn
})

//
// sleep
it('should return a promise for pausing execution by calling setTimeout', async () => {
const mockSetTimeout = jest.fn()
global.setTimeout = (fn: Function, milliseconds: number) => {
mockSetTimeout(milliseconds)
return fn()
}

await sleep(1337)

expect(mockSetTimeout).toHaveBeenCalledWith(1337)
})

})

0 comments on commit 3c9fa57

Please sign in to comment.