-
-
Save sunil-sangwan/f3be83c37fa56e395175b20829311054 to your computer and use it in GitHub Desktop.
Download all Payslips from Keka HR Software
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This puppeteer script will automatically download all payslips for you, if your company uses Keka HR Software. | |
* I downloaded about 4 years of payslips in approximately 2 minutes! :P | |
* | |
* - Download this file and save it in some temporary directory. | |
* - Fill in the params wherever you see the ℹ️ in this file & save. | |
* - Install the dependency `puppeteer-core` (just run `npm init` and `npm install --save puppeteer-core@2` in your directory). | |
* - Requires NodeJS >= 12 (tested with v12.13.1), puppeteer-core@2 & Google Chrome browser. | |
* - Run: `node downloadPayslips.js` | |
* - The files are all saved to the browser's default download folder. | |
* - If there are any missing months, or if it happens to encounter any error, the script will take a screenshot & exit. | |
* | |
* License: MIT | |
*/ | |
const puppeteer = require('puppeteer-core'); | |
const data = { | |
/** | |
* 📝 | |
* Manually copy the cookies over from your existing logged in session from your company's Keka HR Software. | |
* This allows access to your keka account so, goes without saying, to be careful with where these cookies are shared! 🤓 | |
*/ | |
cookies: [ | |
{ | |
"name": "__RequestVerificationToken", | |
"value": "<PASTE_VALUE_HERE>", // <-- PASTE HERE ℹ️ | |
"httpOnly": true | |
}, | |
{ | |
"name": "ASP.NET_SessionId", | |
"value": "<PASTE_VALUE_HERE>", // <-- PASTE HERE ℹ️ | |
"httpOnly": true | |
}, | |
{ | |
"name": ".AspNet.Cookies", | |
"value": "<PASTE_VALUE_HERE>", // <-- PASTE HERE ℹ️ | |
"httpOnly": true, | |
"secure": true | |
} | |
], | |
/** | |
* 📝 | |
* Parameters of which months of payslips to be downloaded. Both start & end periods are inclusive. | |
* Other parameters required by this script. | |
*/ | |
params: { | |
startYear: 2020, startMonth: 1, // <-- set the start year and month here (months start from 1 (Jan)) ℹ️ | |
endYear: 2020, endMonth: 3, // <-- set the end year and month here (months start from 1 (Jan)) ℹ️ | |
chromePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' // path to Chrome browser installed on your system, the default here is for MacOS, change accordingly ℹ️ | |
kekaDomain: 'example.keka.com', // the subdomain of your company on the keka.com website. ℹ️ | |
}, | |
}; | |
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; | |
const getIterations = (options) => { | |
const { startYear, startMonth, endYear, endMonth } = options; | |
let totalIterations = []; | |
for (let year = startYear; year <= endYear; year++) { | |
let currentMonths = months.map((month, index) => ({ | |
year, | |
month: index + 1, | |
name: month, | |
})); | |
if (year === startYear) { | |
currentMonths = currentMonths.filter(month => month.month >= startMonth); | |
} | |
if (year === endYear) { | |
currentMonths = currentMonths.filter(month => month.month <= endMonth); | |
} | |
totalIterations.push({ | |
year, | |
months: currentMonths, | |
}); | |
} | |
return totalIterations; | |
} | |
const iterations = getIterations(data.params); | |
const download = async () => { | |
const browser = await puppeteer.launch({ | |
headless: false, | |
executablePath: data.params.chromePath, | |
devtools: false, | |
}); | |
const page = await browser.newPage(); | |
page.setViewport({ | |
width: 1366, | |
height: 768, | |
deviceScaleFactor: 1 | |
}); | |
// setup | |
await page.setCookie(...data.cookies.map(cookie => ({ ...cookie, domain: data.params.kekaDomain }))); | |
await page.goto(`https://${data.params.kekaDomain}/old/#/finances/mypay/payslips`, { waitUntil: 'networkidle2' }); | |
for (let iteration of iterations) { | |
const { year, months } = iteration; | |
console.log(`running ${months.length} iterations for year ${year}`); | |
console.table(months); | |
// select year | |
await page.click('.pay-slip-year > div > button'); | |
const [yearItem] = await page.$x(`//a[contains(.,${year})]`); | |
if (!yearItem) { | |
console.warn(`Unable to select year ${year}`); | |
page.screenshot({ path: `${year}--not-found.png` }); | |
continue; | |
} | |
await yearItem.click(); | |
await page.waitFor(5000); | |
// select each month | |
for (let month of months) { | |
// select month | |
const { name } = month; | |
const [monthButton] = await page.$x(`//a[contains(.,\'${name}\')]`); | |
if (!monthButton) { | |
console.warn(`Unable to select month ${year}-${month}`); | |
await page.screenshot({ path: `${year}-${name}-not-found.png` }); | |
continue; | |
} | |
await monthButton.click(); | |
await page.waitFor(1000); | |
// ensure correct month | |
const headings = await page.$x(`//h2[contains(.,'${name} ${year}')]`); | |
if (!headings.length) { | |
console.warn(`Month heading not found`); | |
await page.screenshot({ path: `${year}-${name}-heading-not-found.png` }); | |
continue; | |
} | |
const [downloadButton] = await page.$x(`//a[contains(.,\'Download Payslip\')]`); | |
if (!downloadButton) { | |
console.warn(`Download button not found`); | |
await page.screenshot({ path: `${year}-${name}-download-button-not-found.png` }); | |
continue; | |
} | |
// download it | |
await downloadButton.click(); | |
await page.waitForResponse(response => { | |
const isDownload = response.url().indexOf('api/myfinances/payslip/export/') !== -1; | |
return isDownload && response.status() === 200; | |
}); | |
await page.waitFor(750); | |
} | |
} | |
console.log('success!'); | |
await browser.close(); | |
}; | |
try { | |
download(); | |
} catch (e) { | |
console.error(e); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Forked Main script