Update password field extraction (#541)

This commit is contained in:
Leendert de Borst
2025-02-14 21:31:31 +01:00
parent 8c132f30fb
commit fbf7f5b4e4
4 changed files with 220 additions and 10 deletions

View File

@@ -201,7 +201,7 @@ export class FormDetector {
// Find primary email field
const primaryEmail = this.findInputField(
form,
['email', 'e-mail', 'mail', 'address', '@'],
['email', 'e-mail', 'mail', '@', 'emailaddress'],
['text', 'email']
);
@@ -401,9 +401,6 @@ export class FormDetector {
return patterns.some(pattern => attributes.some(attr => attr.includes(pattern)));
}
/**
* Find the password field in a form.
*/
/**
* Find the password field in a form.
*/
@@ -423,7 +420,6 @@ export class FormDetector {
const attributes = [
input.id,
input.name,
input.className,
input.placeholder
].map(attr => attr?.toLowerCase() || '');
@@ -434,6 +430,11 @@ export class FormDetector {
}
}
// If no clear primary password field is found, use the first password field as primary
if (!primaryPassword && candidates.length > 0) {
primaryPassword = candidates[0];
}
// If we found a primary password, look for a confirmation field
if (primaryPassword) {
for (const input of Array.from(candidates)) {
@@ -454,11 +455,6 @@ export class FormDetector {
}
}
// If no clear primary password field is found, use the first password field as primary
if (!primaryPassword && candidates.length > 0) {
primaryPassword = candidates[0];
}
return {
primary: primaryPassword,
confirm: confirmPassword

View File

@@ -64,6 +64,13 @@ describe('FormDetector', () => {
testField(FormField.Email, 'field18478', htmlFile);
testField(FormField.FirstName, 'field18479', htmlFile);
testField(FormField.LastName, 'field18486', htmlFile);
});
describe('Dutch registration form 7 detection', () => {
const htmlFile = 'nl-registration-form7.html';
testField(FormField.Email, 'Form_EmailAddress', htmlFile);
testField(FormField.Password, 'Form_Password', htmlFile);
testField(FormField.PasswordConfirm, 'Form_RepeatPassword', htmlFile);
});
});

View File

@@ -0,0 +1,202 @@
import { FormDetector } from '../FormDetector';
import { readFileSync } from 'fs';
import { join } from 'path';
import { describe, it, expect } from 'vitest';
import { JSDOM } from 'jsdom';
import { LoginForm } from '../types/LoginForm';
/**
* Load a test HTML file.
*/
const loadTestHtml = (filename: string): string => {
return readFileSync(join(__dirname, 'test-forms', filename), 'utf-8');
};
/**
* Setup a form detection test.
*/
const setupFormTest = (htmlFile: string, focusedElementId?: string) : { document: Document, result: LoginForm } => {
const html = loadTestHtml(htmlFile);
const dom = new JSDOM(html, {
url: 'http://localhost',
runScripts: 'dangerously',
resources: 'usable'
});
const document = dom.window.document;
// Set focus on specified element if provided
let focusedElement: HTMLElement | null = null;
if (focusedElementId) {
focusedElement = document.getElementById(focusedElementId);
if (!focusedElement) {
throw new Error(`Focus element with id "${focusedElementId}" not found in test HTML`);
}
focusedElement.focus();
// Create a new form detector with the focused element.
const formDetector = new FormDetector(document, focusedElement);
const result = formDetector.detectForms()[0];
return { document, result };
}
// No focused element, so just detect the first form.
const formDetector = new FormDetector(document);
const result = formDetector.detectForms()[0];
return { document, result };
};
enum FormField {
Username = 'username',
FirstName = 'firstName',
LastName = 'lastName',
Email = 'email',
EmailConfirm = 'emailConfirm',
Password = 'password',
PasswordConfirm = 'passwordConfirm',
BirthDate = 'birthdate',
BirthDateFormat = 'birthdateFormat',
BirthDay = 'birthdateDay',
BirthMonth = 'birthdateMonth',
BirthYear = 'birthdateYear',
Gender = 'gender',
GenderMale = 'genderMale',
GenderFemale = 'genderFemale',
GenderOther = 'genderOther'
}
/**
* Helper function to test field detection
*/
const testField = (fieldName: FormField, elementId: string, htmlFile: string) : void => {
it(`should detect ${fieldName} field`, () => {
const { document, result } = setupFormTest(htmlFile, elementId);
// First verify the test element exists
const expectedElement = document.getElementById(elementId);
if (!expectedElement) {
throw new Error(`Test setup failed: Element with id "${elementId}" not found in test HTML. Check if the element is present in the test HTML file: ${htmlFile}`);
}
// Handle birthdate fields differently
if (fieldName === FormField.BirthDate) {
expect(result.birthdateField.single).toBe(expectedElement);
} else if (fieldName === FormField.BirthDay) {
expect(result.birthdateField.day).toBe(expectedElement);
} else if (fieldName === FormField.BirthMonth) {
expect(result.birthdateField.month).toBe(expectedElement);
} else if (fieldName === FormField.BirthYear) {
expect(result.birthdateField.year).toBe(expectedElement);
}
// Handle gender field differently
else if (fieldName === FormField.Gender) {
expect(result.genderField.field).toBe(expectedElement);
} else if (fieldName === FormField.GenderMale) {
expect(result.genderField.radioButtons?.male).toBe(expectedElement);
} else if (fieldName === FormField.GenderFemale) {
expect(result.genderField.radioButtons?.female).toBe(expectedElement);
} else if (fieldName === FormField.GenderOther) {
expect(result.genderField.radioButtons?.other).toBe(expectedElement);
}
// Handle default fields
else {
const fieldKey = `${fieldName}Field` as keyof typeof result;
expect(result[fieldKey]).toBeDefined();
expect(result[fieldKey]).toBe(expectedElement);
}
});
};
/**
* Test the birthdate format.
*/
const testBirthdateFormat = (expectedFormat: string, htmlFile: string) : void => {
it('should detect correct birthdate format', () => {
const { result } = setupFormTest(htmlFile);
expect(result.birthdateField.format).toBe(expectedFormat);
});
};
describe('FormDetector', () => {
describe('Dutch registration form detection', () => {
const htmlFile = 'nl-registration-form1.html';
testField(FormField.LastName, 'cpContent_txtAchternaam', htmlFile);
testField(FormField.Email, 'cpContent_txtEmail', htmlFile);
testField(FormField.Password, 'cpContent_txtWachtwoord', htmlFile);
testField(FormField.PasswordConfirm, 'cpContent_txtWachtwoord2', htmlFile);
});
describe('Dutch registration form 2 detection', () => {
const htmlFile = 'nl-registration-form2.html';
testField(FormField.Username, 'register-username', htmlFile);
testField(FormField.Email, 'register-email', htmlFile);
testField(FormField.Password, 'register-password', htmlFile);
testField(FormField.BirthDay, 'register-day', htmlFile);
testField(FormField.BirthMonth, 'register-month', htmlFile);
testField(FormField.BirthYear, 'register-year', htmlFile);
testField(FormField.GenderMale, 'man', htmlFile);
testField(FormField.GenderFemale, 'vrouw', htmlFile);
testField(FormField.GenderOther, 'iets', htmlFile);
});
describe('Dutch registration form 3 detection', () => {
const htmlFile = 'nl-registration-form3.html';
testField(FormField.FirstName, 'firstName', htmlFile);
testField(FormField.LastName, 'lastName', htmlFile);
testField(FormField.Password, 'password', htmlFile);
testField(FormField.BirthDate, 'date', htmlFile);
testBirthdateFormat('dd-mm-yyyy', htmlFile);
testField(FormField.GenderMale, 'gender1', htmlFile);
testField(FormField.GenderFemale, 'gender2', htmlFile);
testField(FormField.GenderOther, 'gender3', htmlFile);
});
describe('Dutch registration form 4 detection', () => {
const htmlFile = 'nl-registration-form4.html';
testField(FormField.Email, 'EmailAddress', htmlFile);
});
describe('Dutch registration form 5 detection', () => {
const htmlFile = 'nl-registration-form5.html';
testField(FormField.Email, 'input_25_5', htmlFile);
testField(FormField.Gender, 'input_25_13', htmlFile);
testField(FormField.FirstName, 'input_25_14', htmlFile);
testField(FormField.LastName, 'input_25_15', htmlFile);
testField(FormField.BirthDate, 'input_25_10', htmlFile);
testBirthdateFormat('dd/mm/yyyy', htmlFile);
});
describe('English registration form 1 detection', () => {
const htmlFile = 'en-registration-form1.html';
testField(FormField.Email, 'login', htmlFile);
testField(FormField.Password, 'password', htmlFile);
});
describe('English registration form 2 detection', () => {
const htmlFile = 'en-registration-form2.html';
testField(FormField.Email, 'signup-email-input', htmlFile);
testField(FormField.Username, 'signup-name-input', htmlFile);
});
describe('English registration form 3 detection', () => {
const htmlFile = 'en-registration-form3.html';
testField(FormField.Email, 'email', htmlFile);
testField(FormField.EmailConfirm, 'reenter_email', htmlFile);
});
describe('English email form 1 detection', () => {
const htmlFile = 'en-email-form1.html';
testField(FormField.Email, 'P0-0', htmlFile);
});
});

View File

File diff suppressed because one or more lines are too long