FormsRefresh

Home / Demo

HR application

Three Forms modules on Oracle's HR sample schema: LOGIN, CONSOLE and EMPLOYEES (master-detail blocks on tab pages, three lists of values, alerts, calls between forms, APEX links signed with a token).

Open the live migrated app Same app, Angular front end Scenarios and diffs Faithful to the original

Target: .NET 10 and PostgreSQL, the forms' windows, blocks, lists of values and alerts in the browser. Original Forms source: github.com/astramieri/hr-app-forms at commit 5d6c227, licence: MIT, copyright 2024 Angelo Stramieri.

Demo login: Log in as alice / alice#123 (administrator) or bob / bob#123.

The live demos share one database per app: visitors see each other's changes, and the data resets to the scenario fixture every night.

Parity: 17 of 17 scenarios

Each scenario was recorded on the original and replayed against the migrated app. Three runs from a fresh database gave byte-identical diffs for every scenario; none has a defect. Open a scenario for its steps, screenshots of both apps, the golden record, the diff and its waivers.

ScenarioWhat it doesmatchtoleratedwaiveddefectunmappedThree runs
console-apexThe APEX button composes a URL with a signed token (COMPOSE_URL), shows it and calls WEB.SHOW_DOCUMENT.391300identical
console-logoutLog in as bob; LOGOUT erases the global and returns to LOGIN with NEW_FORM.440000identical
employees-apexThe block's APEX button and a row's Details button each compose an APEX URL with a signed token; the client host has no browser, so WEB.SHOW_DOCUMENT answers with FRM-92022. Closing EMPLOYEES ends its database session (OPEN_FORM with SESSION), which writes that session's PL/SQL coverage.65641000identical
employees-change-departmentMove an employee to another department through the DEPARTMENTS LOV and raise another employee's salary, then save. UPDATE_JOB_HISTORY records the old department for the first row and finds nothing to record for the second (Forms updates every column, so the trigger fires for both). Closing EMPLOYEES ends its database session, which writes that session's PL/SQL coverage.5511000identical
employees-change-jobChange an employee's job through the JOBS LOV and save; the UPDATE_JOB_HISTORY trigger records the old job. Closing EMPLOYEES ends its database session, which writes that session's PL/SQL coverage.5511000identical
employees-clearClear All with no change does nothing; after an edit CONFIRM_CLEAR asks, Cancel keeps it and OK discards it.9210000identical
employees-closeClosing EMPLOYEES with an unsaved change asks to commit (EXIT_FORM ASK_COMMIT); No returns to CONSOLE, whose close ends the session.4290000identical
employees-createCreate an employee with the three LOVs and save; PRE-INSERT takes the id from EMPLOYEES_SEQ.10463700identical
employees-deleteDelete an employee without history after confirming CONFIRM_DELETE; DELETE_RECORD and COMMIT_FORM.5520000identical
employees-delete-refusedDeleting an employee with job history is refused by ON-CHECK-DELETE-MASTER; a cancelled delete changes nothing.7980000identical
employees-detailsMove to another employee (the relations clear and requery the details) and show each tab page; Save with nothing changed does not commit.7980000identical
employees-openLog in as alice and open EMPLOYEES from CONSOLE; WHEN-NEW-FORM-INSTANCE queries the employees.1610000identical
employees-searchSearch employees by a name fragment (the first hit has no department, so its DEPARTMENT detail is not queried), Reset back to the full list, then Search with an empty field.6600100identical
login-closeClosing the LOGIN window (title-bar close box) runs WHEN-WINDOW-CLOSED, which ends the session.260000identical
login-failA wrong password raises ALERT_FAILURE (NO_DATA_FOUND in LOGIN.LOGIN); LOGIN stays open.500000identical
login-okLog in as alice; LOGIN opens CONSOLE with NEW_FORM, which shows the user name.360000identical
login-openOpen LOGIN and record its initial state (the prefix of the LOGIN locator check).200000identical
total7338102100

The same app with an Angular front end: 17 of 17

The check does not depend on what the new interface is built with. This app has a second front end written in Angular on the same back end and API, and the same scenarios, mapping, tolerance policy and waivers check it: 17 of 17 scenarios at parity, 0 defects, three runs from a fresh database byte-identical, and every diff byte-identical to the default front end's.

ScenariomatchtoleratedwaiveddefectunmappedThree runsDiff as the default front end
console-apex391300identicalidentical
console-logout440000identicalidentical
employees-apex65641000identicalidentical
employees-change-department5511000identicalidentical
employees-change-job5511000identicalidentical
employees-clear9210000identicalidentical
employees-close4290000identicalidentical
employees-create10463700identicalidentical
employees-delete-refused7980000identicalidentical
employees-delete5520000identicalidentical
employees-details7980000identicalidentical
employees-open1610000identicalidentical
employees-search6600100identicalidentical
login-close260000identicalidentical
login-fail500000identicalidentical
login-ok360000identicalidentical
login-open200000identicalidentical

The app is also rebuilt from native APEX components on its original Oracle schema, checked with the same scenarios: the APEX version.

Faithful to the original

The migrated app copies the original's behaviour, including what looks like a bug. These are the places a visitor will notice.

The interface is new; the behaviour is the original's, and the scenarios check it value by value. Some of that behaviour looks like a bug on a web page. It is kept on purpose, because the point of the demo is that nothing changed:

Deliberate differences that no scenario covers: Enter in the login and search fields presses Login and Search (Forms moves to the next field); the server requires a logged-in session for its data calls; the employee list scrolls with the mouse wheel and its own scrollbar.

Forms + APEX

The original HR app is a Forms + APEX hybrid. Its Home, Employees and row Details buttons compose a signed token (COMPOSE_URL, GENERATE_TOKEN) and open pages of a separate Oracle APEX application, hr_dev/hr-app, in a new tab. That APEX application is not part of the public source: the upstream repository at the pinned commit holds only the three Forms modules and the database code. It was therefore not migrated, and in the recorded original the buttons fail with FRM-92022 (waiver H1).

In this demo the buttons open stand-in pages under /hr/apex/ (/hr-ng/apex/ from the Angular front end). Each page says that it stands in for the APEX application, verifies the token as APEX would (signature, audience, five-minute expiry) and shows the data the APEX page would show. Migrating the APEX side would be its own scenario set, recorded on the APEX application.

Coverage of the original

Every trigger and program unit of the Forms modules, measured with Forms Runtime Diagnostics while the scenarios ran; database PL/SQL measured with DBMS_PLSQL_CODE_COVERAGE.

ModuleTriggersProgram unitsCoveredUnreachableBlocked
CONSOLE50500
EMPLOYEES1431700
LOGIN20200
total2132400

Decision branches: 32, of which 24 covered, 4 unreachable (retirement candidates) and 4 blocked. Database PL/SQL: 11 unit(s) in the coverage report, 13 of 13 blocks covered. Exit check: PASS.

Every branch with its class
Trigger or unitBranchClassScenarios or reason
LOGIN/LOGIN.LOGIN/WHEN-BUTTON-PRESSEDuser and password match - console openscoveredconsole-apex, console-logout, employees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search, login-ok
LOGIN/LOGIN.LOGIN/WHEN-BUTTON-PRESSEDno matching user - ALERT_FAILUREcoveredlogin-fail
CONSOLE/CONSOLE.EMPLOYEES/WHEN-BUTTON-PRESSEDEMPLOYEES not open - OPEN_FORMcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
CONSOLE/CONSOLE.EMPLOYEES/WHEN-BUTTON-PRESSEDEMPLOYEES already open - GO_FORMblockedNeeds the CONSOLE button pressed while EMPLOYEES is open. OPEN_FORM(ACTIVATE) puts the EMPLOYEES window over the console, and the way back is the FSAL "Window" menu, which is a client menu with no Forms name (LWMenu1); the runner only clicks named elements. Harness limit.
EMPLOYEES/BUTTONS.SAVE_BUTTON/WHEN-BUTTON-PRESSEDblock changed - commitcoveredemployees-change-department, employees-change-job, employees-create
EMPLOYEES/BUTTONS.SAVE_BUTTON/WHEN-BUTTON-PRESSEDnothing changed - no commitcoveredemployees-details
EMPLOYEES/BUTTONS.DELETE_BUTTON/WHEN-BUTTON-PRESSEDconfirmed - delete and commitcoveredemployees-delete
EMPLOYEES/BUTTONS.DELETE_BUTTON/WHEN-BUTTON-PRESSEDcancelled - nothing deletedcoveredemployees-delete-refused
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSEDchanged, confirmed - changes discarded and requeriedcoveredemployees-clear
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSEDchanged, cancelled - changes keptcoveredemployees-clear
EMPLOYEES/BUTTONS.CLEAR_BUTTON/WHEN-BUTTON-PRESSEDnothing changed - no questioncoveredemployees-clear
EMPLOYEES/EMPLOYEES/ON-CHECK-DELETE-MASTERjob history exists, delete refusedcoveredemployees-delete-refused
EMPLOYEES/EMPLOYEES/ON-CHECK-DELETE-MASTERno job history, delete goes aheadcoveredemployees-delete
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSmaster record NEW or INSERT - no detail querycoveredemployees-clear, employees-create
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSDEPARTMENT_ID set - department queriedcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSDEPARTMENT_ID null - no department querycoveredemployees-search
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSMANAGER_ID set - manager queriedcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-delete, employees-delete-refused, employees-details, employees-search
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSMANAGER_ID null - no manager querycoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSEMPLOYEE_ID set - history queriedcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILSEMPLOYEE_ID null on a queried record - no history queryunreachableEMPLOYEE_ID is the primary key of EMPLOYEES, so a queried record always has it; NEW and INSERT records return before this point.
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILScursor moved by the queries, returned to the start itemcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/EMPLOYEES/ON-POPULATE-DETAILScursor still on the start item after the queriesunreachableEvery relation is not deferred, so each detail query does GO_BLOCK to the detail and the cursor always leaves the master item; HISTORY is always queried (EMPLOYEE_ID is the key).
EMPLOYEES/unit:QUERY_MASTER_DETAILSrelation not deferred - GO_BLOCK and EXECUTE_QUERYcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/unit:QUERY_MASTER_DETAILSrelation deferred - SET_BLOCK_PROPERTY non-coordinatedunreachableNo relation of EMPLOYEES is deferred and nothing changes that at run time.
EMPLOYEES/unit:QUERY_MASTER_DETAILSGO_BLOCK to the detail failed - exception handlerblockedNeeds GO_BLOCK to a detail block to fail during coordination. The detail blocks have no PRE-BLOCK, WHEN-NEW-BLOCK-INSTANCE or validation that can refuse entry; no input found that makes it fail.
EMPLOYEES/unit:CHECK_PACKAGE_FAILUREprevious built-in succeededcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/unit:CHECK_PACKAGE_FAILUREprevious built-in failed - raise FORM_TRIGGER_FAILUREblockedCalled after GO_BLOCK and GO_ITEM in the coordination units; no input found that makes these fail (see the QUERY_MASTER_DETAILS exception branch).
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILSa detail block holds rows - cleared without validationcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-search
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILSevery detail block empty - nothing clearedcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-open, employees-search
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILScursor moved by the clearing, returned to the start itemcoveredemployees-apex, employees-change-department, employees-change-job, employees-clear, employees-close, employees-create, employees-delete, employees-delete-refused, employees-details, employees-search
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILSchanged detail block below the master - ask to commitunreachableDEPARTMENT, MANAGER and HISTORY allow no insert, update or delete, so a detail block is never CHANGED.
EMPLOYEES/unit:CLEAR_ALL_MASTER_DETAILSa detail block not cleared - raise FORM_TRIGGER_FAILUREblockedNeeds CLEAR_BLOCK(NO_VALIDATE) on a read-only detail block to leave rows behind; no input found that makes it fail.
Database PL/SQL units
UnitTypeBlocksCoveredClass
COMPOSE_URLFUNCTION33covered
GENERATE_TOKENFUNCTION55covered
VALIDATE_TOKENFUNCTIONNone0unreachable
ADD_JOB_HISTORYPROCEDURE11covered
INSERT_LOGPROCEDURENone0unreachable
SECURE_DMLPROCEDURENone0unreachable
APEX_TRG_01TRIGGERNone0unreachable
REQUESTS_TRG_01TRIGGERNone0unreachable
SECURE_EMPLOYEESTRIGGERNone0unreachable
UPDATE_JOB_HISTORYTRIGGER44covered
USERS_TRG_01TRIGGERNone0unreachable

Waivers

Source behaviour a customer would probably not replicate, proposed for the customer's decision. Each is pinned to the exact values it covers; the counts are values waived in the final diffs. File: waivers.yaml.

Waived: 2 waivers are proposed for the customer's decision; 2 of them cover 21 values here, where the migrated app deliberately does not copy the original (see waivers).

IdWaiverValuesReason
H1APEX links fail with FRM-92022 instead of opening the page13The APEX buttons (CONSOLE.APEX, BUTTONS.APEX_BUTTON, the EMPLOYEES.APEX row button) compose a signed APEX URL, show it on the message line and call WEB.SHOW_DOCUMENT(url, '_blank'). On the Forms 14.1.2 client this fails: Forms shows the alert "FRM-92022: cannot launch URL ... The BROWSE action is not supported on the current platform!" and the page never opens, so the source records an alert, alert focus and an extra window, and the user has to dismiss it. The target writes the same message and opens the URL in a new browser tab, which is what the code asks for. Replicating the failure would mean shipping a broken link. The message line (APEX URL with the token) is still compared.
H2Department tab keeps the previous department number8When the current employee has no department (a new record, or Kimberely Grant, whose DEPARTMENT_ID is NULL), Forms clears the DEPARTMENT detail block, yet DEPARTMENT_ID on the Department tab keeps showing the number of the department shown before (90, Executive) while name, address, city and region are blank. The number belongs to another employee's department; the target clears the whole tab.