138 lines
5.0 KiB
JavaScript
138 lines
5.0 KiB
JavaScript
// @ts-check
|
|||
|
|
const { test, expect } = require('@playwright/test');
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
const RESULT_PATH = path.join(__dirname, '..', 'test-results', 'result.json');
|
||
|
|
|
||
|
|
function writeResult(result) {
|
||
|
|
fs.mkdirSync(path.dirname(RESULT_PATH), { recursive: true });
|
||
|
|
fs.writeFileSync(RESULT_PATH, JSON.stringify(result));
|
||
|
|
}
|
||
|
|
|
||
|
|
test.describe('linkedin post', () => {
|
||
|
|
let outcome = { success: false, error: 'Test did not complete' };
|
||
|
|
let outcomeFinalized = false;
|
||
|
|
|
||
|
|
test.afterEach(async () => {
|
||
|
|
writeResult(outcome);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('logs into LinkedIn and creates a post', async ({ page }) => {
|
||
|
|
const email = process.env.LINKEDIN_EMAIL;
|
||
|
|
const password = process.env.LINKEDIN_PASSWORD;
|
||
|
|
const postContent = process.env.POST_CONTENT;
|
||
|
|
|
||
|
|
for (const [name, value] of Object.entries({
|
||
|
|
LINKEDIN_EMAIL: email,
|
||
|
|
LINKEDIN_PASSWORD: password,
|
||
|
|
POST_CONTENT: postContent,
|
||
|
|
})) {
|
||
|
|
if (!value) {
|
||
|
|
outcome = { success: false, error: `Missing required environment variable: ${name}` };
|
||
|
|
outcomeFinalized = true;
|
||
|
|
throw new Error(outcome.error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
await page.goto('https://www.linkedin.com/login');
|
||
|
|
|
||
|
|
// Wait for page to load
|
||
|
|
await page.waitForLoadState('networkidle');
|
||
|
|
|
||
|
|
// Check for login form
|
||
|
|
const emailField = page.getByLabel(/email|phone/i).first();
|
||
|
|
if (!await emailField.isVisible({ timeout: 10000 }).catch(() => false)) {
|
||
|
|
outcome = {
|
||
|
|
success: false,
|
||
|
|
error: 'LinkedIn login form not found — page structure may have changed',
|
||
|
|
};
|
||
|
|
outcomeFinalized = true;
|
||
|
|
throw new Error(outcome.error);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fill email
|
||
|
|
await emailField.fill(email);
|
||
|
|
|
||
|
|
// Fill password
|
||
|
|
const passwordField = page.getByLabel(/password/i).first();
|
||
|
|
if (await passwordField.isVisible({ timeout: 10000 }).catch(() => false)) {
|
||
|
|
await passwordField.fill(password);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Click sign in button
|
||
|
|
const signInButton = page.getByRole('button', { name: /sign in|signin|log in/i, exact: false }).first();
|
||
|
|
await signInButton.click();
|
||
|
|
|
||
|
|
// Wait for login to complete - check for feed or start post button
|
||
|
|
try {
|
||
|
|
await page.waitForURL('https://www.linkedin.com/feed/', { timeout: 20000 });
|
||
|
|
} catch {
|
||
|
|
// Check if we're logged in by looking for start post button
|
||
|
|
const startPostButton = page.getByRole('button', { name: /start a post|post/i, exact: false }).first();
|
||
|
|
if (!await startPostButton.isVisible({ timeout: 10000 }).catch(() => false)) {
|
||
|
|
const errorText = await page.textContent('body').catch(() => '');
|
||
|
|
outcome = {
|
||
|
|
success: false,
|
||
|
|
error: errorText.toLowerCase().includes('invalid') || errorText.toLowerCase().includes('incorrect')
|
||
|
|
? 'Invalid LinkedIn credentials'
|
||
|
|
: 'LinkedIn login did not complete — possible CAPTCHA, 2FA, or invalid credentials',
|
||
|
|
};
|
||
|
|
outcomeFinalized = true;
|
||
|
|
throw new Error(outcome.error);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Click "Start a post" button
|
||
|
|
const startPostButton = page.getByRole('button', { name: /start a post|post/i, exact: false }).first();
|
||
|
|
await startPostButton.click();
|
||
|
|
|
||
|
|
// Wait for post composer
|
||
|
|
const postTextarea = page.getByRole('textbox', { name: /what do you want to talk about/i, exact: false }).first();
|
||
|
|
if (!await postTextarea.isVisible({ timeout: 10000 }).catch(() => false)) {
|
||
|
|
// Try alternative selector
|
||
|
|
const altTextarea = page.locator('div[contenteditable="true"]').first();
|
||
|
|
if (await altTextarea.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||
|
|
await altTextarea.click();
|
||
|
|
await page.keyboard.type(postContent);
|
||
|
|
} else {
|
||
|
|
outcome = {
|
||
|
|
success: false,
|
||
|
|
error: 'LinkedIn post composer not found — UI may have changed',
|
||
|
|
};
|
||
|
|
outcomeFinalized = true;
|
||
|
|
throw new Error(outcome.error);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
await postTextarea.fill(postContent);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Click post button
|
||
|
|
const postButton = page.getByRole('button', { name: /^post$/i, exact: true }).first();
|
||
|
|
if (await postButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||
|
|
await postButton.click();
|
||
|
|
} else {
|
||
|
|
// Try alternative post button selectors
|
||
|
|
const altPostButton = page.getByRole('button', { name: /share|post/i, exact: false }).nth(1);
|
||
|
|
if (await altPostButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||
|
|
await altPostButton.click();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wait for post to be created
|
||
|
|
await page.waitForTimeout(3000);
|
||
|
|
|
||
|
|
// Get current URL (should be the post URL or feed)
|
||
|
|
outcome = { success: true, url: page.url() };
|
||
|
|
outcomeFinalized = true;
|
||
|
|
expect(outcome.success).toBe(true);
|
||
|
|
} catch (err) {
|
||
|
|
if (!outcomeFinalized) {
|
||
|
|
outcome = { success: false, error: err instanceof Error ? err.message : String(err) };
|
||
|
|
}
|
||
|
|
throw err;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|