Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
maximfromit committed Aug 20, 2024
0 parents commit a5214b8
Show file tree
Hide file tree
Showing 13 changed files with 1,062 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules/
/test-results/
/playwright-report/
/playwright/.cache/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# 🐺 QA Wolf Take Home Assignment

Welcome to the QA Wolf take home assignment for our [QA Engineer](https://www.notion.so/qawolf/QA-Wolf-QA-Engineer-Remote-156203a1e476459ea5e6ffca972d0efe) role! We appreciate your interest and look forward to seeing what you come up with.

## Instructions

This assignment has two questions as outlined below. When you are done, send [[email protected]](mailto:[email protected]) the following:

1. A link to a zip file of this folder on Google Drive

2. A note indicating your work location (Country/State)

3. A note of how you found this job post (LinkedIn, Handshake, Wellfound, referral, etc.)

### Question 1

In this assignment, you will create a script on [Hacker News](https://news.ycombinator.com/) using JavaScript and Microsoft's [Playwright](https://playwright.dev/) framework.

1. Install node modules by running `npm i`.

2. Edit the `index.js` file in this project to go to [Hacker News/newest](https://news.ycombinator.com/newest) and validate that EXACTLY the first 100 articles are sorted from newest to oldest. You can run your script with the `node index.js` command.

Note that you are welcome to update Playwright or install other packages as you see fit, however you must utilize Playwright in this assignment.

### Question 2

Why do you want to work at QA Wolf? Please record a short, ~2 min video that includes:

1. Your answer

2. A walk-through demonstration of your code, showing a successful execution

Post the link in `why_qa_wolf.txt` (Please use [Loom](https://www.loom.com) to record your response). The answer and walkthrough should be combined into *one* video.

## Frequently Asked Questions

### What is your hiring process? When will I hear about next steps?

This take home assignment is the first step in our hiring process, followed by a final round interview if it goes well. **We review every take home assignment submission and promise to get back to you either way within one week (usually sooner).** The only caveat is if we are out of the office, in which case we will get back to you when we return. If it has been more than one week and you have not heard from us, please do follow up.

The final round interview is a 2-hour technical work session that reflects what it is like to work here. We provide a $150 stipend for your time for the final round interview regardless of how it goes. After that, there may be a short chat with our director about your experience and the role.

Our hiring process is rolling where we review candidates until we have filled our openings. If there are no openings left, we will keep your contact information on file and reach out when we are hiring again.

### How do you decide who to hire?

We evaluate candidates based on three criteria:

- Technical ability (as demonstrated in the take home and final round)
- Customer service orientation (as this role is customer facing)
- Alignment with our values (captured [here](https://www.notion.so/qawolf/QA-Wolf-QA-Engineer-Remote-156203a1e476459ea5e6ffca972d0efe))

This means whether we hire you is based on how you do during our interview process, not on your previous experience (or lack thereof). Note that you will also need to pass a background check to work here as our customers require this.

### How can I help my application stand out?

We've found that our best hires have been the most enthusiastic throughout our process. If you are very excited about working here, please feel free to go above and beyond on this assignment.
18 changes: 18 additions & 0 deletions e2e/example.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
await page.goto('https://playwright.dev/');

// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');

// Click the get started link.
await page.getByRole('link', { name: 'Get started' }).click();

// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
16 changes: 16 additions & 0 deletions index-original.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// EDIT THIS FILE TO COMPLETE ASSIGNMENT QUESTION 1
const { chromium } = require("playwright");

async function sortHackerNewsArticles() {
// launch browser
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();

// go to Hacker News
await page.goto("https://news.ycombinator.com/newest");
}

(async () => {
await sortHackerNewsArticles();
})();
114 changes: 114 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { test, expect, chromium } from "@playwright/test"
import dayjs from "dayjs"
import { link } from "fs"

async function sortHackerNewsArticles() {
// launch browser
const browser = await chromium.launch({ headless: false })
const context = await browser.newContext()
const page = await context.newPage()

console.log("Navigating to Hacker News newest page...")
await page.goto("https://news.ycombinator.com/newest")
console.log("Page loaded.")

console.log("Extracting article data...")
// Locate all rows
const rows = await page.getByRole("row")

// Use evaluateAll to get the IDs and ranks
const news = await rows.evaluateAll((rows) => {
// Step 1: Extract id and rank
const newsComputed = rows
.map((row) => {
const id = row.getAttribute("id")

// Find the element with text matching the rank pattern
const isMaxThreeDigitsAndDot = (value) => {
return /^\d{1,3}\.$/.test(value)
}
const findedRank = () => {
const findedRankByClassRank = Array.from(
row.querySelectorAll(".rank")
).find((el) => {
const text = el.textContent.trim()
return isMaxThreeDigitsAndDot(text)
})
const findedRankByAllRow = Array.from(row.querySelectorAll("*")).find(
(el) => {
const text = el.textContent.trim()
return isMaxThreeDigitsAndDot(text)
}
)
if (!!findedRankByClassRank)
return parseInt(
findedRankByClassRank.textContent.trim().replace(".", ""),
10
)
if (!!findedRankByAllRow)
return parseInt(
findedRankByAllRow.textContent.trim().replace(".", ""),
10
)
return null
}

return { id: id ?? null, rank: findedRank() }
})
.filter((item) => item.id !== null && item.rank !== null)

// Step 2: Check for timestamps in subsequent rows
rows.forEach((row) => {
const hrefsInRowForCheck = Array.from(
row.querySelectorAll("a[href]")
).map((a) => a.getAttribute("href"))
const idsInRowForCheck = Array.from(row.querySelectorAll("[id]")).map(
(el) => el.getAttribute("id")
)

newsComputed.forEach((newsItem) => {
if (
hrefsInRowForCheck.some((href) => href.includes(newsItem.id)) ||
idsInRowForCheck.some((id) => id.includes(newsItem.id))
) {
const isTime = (value) => {
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/.test(value)
}

const findedTime = () => {
const findedTimeInTitle = Array.from(
row.querySelectorAll("*")
).find((el) => isTime(el.getAttribute("tit2le")))

const findedTimeInAttributes = Array.from(
row.querySelectorAll("*")
).find((el) => {
return Array.from(el.attributes).some((attr) =>
isTime(attr.value)
)
})

if (findedTimeInTitle)
return findedTimeInTitle.getAttribute("title")
if (findedTimeInAttributes) {
return Array.from(findedTimeInAttributes.attributes).find(
(attr) => isTime(attr.value)
).value
}
return null
}

if (!!findedTime()) newsItem.time = findedTime()
}
})
})

return newsComputed
})

console.log(news)
}

;(async () => {
await sortHackerNewsArticles()
})()
97 changes: 97 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "qa_wolf_take_home",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dayjs": "^1.11.12",
"playwright": "^1.39.0"
},
"devDependencies": {
"@playwright/test": "^1.46.1",
"@types/node": "^20.8.9"
}
}
Loading

0 comments on commit a5214b8

Please sign in to comment.