Overcoming Nepal's Passport Form Challenges with Cypress

Overcoming Nepal’s Passport Form Challenges with Cypress

When Nepal introduced the new E-Passport system, it promised a less stressful application process for everyone. The expectation was that individuals could easily fill out the application themselves, attend the appointment, and receive their passport within a reasonable timeframe. However, the reality proved to be quite different. The system faced immense challenges, unable to handle the server load from users. As a result, the website became unusable to the general public, and intermediaries emerged once again. Like many others, I struggled for over six months to fill out the form. The process seemed straightforward: select an available appointment slot and complete the form. However, due to the server load, the website failed to load consistently. Frustrated, I took matters into my own hands, embarking on a journey of “Overcoming Nepal’s Passport Form Challenges with Cypress.” This is the story of my triumph.

Analyze the system from outside

Initially, my priority was to comprehend the reasons behind the occurrence of error messages such as “Server Error” or “Server is experiencing heavy network traffic,” as well as the unavailability of options in the forms. To investigate, I accessed the developer console of the website to examine any unsuccessful requests. Consequently, I observed that the forms were displayed only when all network requests were successful. Conversely, if even a single request failed, the page would reload, resulting in an error message being presented.

Successful requests

However, when I block any of the request using developer tool, it will display an error.

In case of any failed requests

As evident from the aforementioned scenario, a single failed request was sufficient to trigger the error. Considering the nature of this web application, it is noteworthy that there are over 40 similar requests that have the potential to fail randomly, particularly during periods of high server load. Consequently, the probability of obtaining successful responses for all these requests was quite slim.

Analyze the API endpoints

During instances of low server load, I adopted a strategy to fill forms from appointment centers that were experiencing minimal service seekers. My objective was to secure an appointment slot and, concurrently, to discern the purpose of each specific request. By engaging in this approach, I aimed to gain insights into the function and significance of individual requests within the system.

First Step

Firstly, it would request the version of the application that had a hash for integrity checking.

HTTP
GET /assets/version.json

{
    "version": "2.0.0",
    "hash": "715340df0e1737ab7b24"
}
Version of the application

Then, it would also download the translation files. Interestingly, it gave me an insight into how admin panel works by looking at the translation response.

HTTP
GET /assets/appms-i18n/en.json

{
    "appms-ui.common.breadcrumb.home":"Home",
    "appms-ui.common.breadcrumb.title":"Planning managment",
    "appms-ui.common.breadcrumb.planning.mgmt.global.active":"Global Planning",
    "appms-ui.common.breadcrumb.planning.mgmt.specific.active" : "Plannings specific",
    "appms-ui.common.breadcrumb.holiday.global.active":"Holidays and weekends",
    "appms-ui.common.breadcrumb.constraint.active" : "Constraints managment",
    "appms-ui.common.breadcrumb.appointment" : "Appointment",
    "appms-ui.common.breadcrumb.settings.active" : "Setting",

    "Items per page:": "Items per page:",
    "Next page": "Next page",
    "Previous page": "Previous page",
    "First page": "First page",
    "Last page": "Last page",
  
    "appms-ui.add.new.planning":"Create new planning",

    "appms-ui.global.planning.page.filter.name" : "Planning Name",
    "appms-ui.global.planning.page.filter.start.date" : "Start Date",
    "appms-ui.global.planning.page.filter.end.date" : "End Date",
    "appms-ui.global.planning.page.filter.status" : "Status",
    "appms-ui.global.planning.page.filter.btn" : "FILTER",

    "appms-ui.global.planning.page.table.col1" : "Planning Name",
    "appms-ui.global.planning.page.table.col2" : "Start Date",
    "appms-ui.global.planning.page.table.col3" : "End Date",
    "appms-ui.global.planning.page.table.col4" : "Status",
    "appms-ui.global.planning.page.table.btn1" : "Planning overview",
    "appms-ui.global.planning.page.table.btn2" : "Edit the planning",
    "appms-ui.global.planning.page.table.btn3" : "Delete the planning",

    "appms-ui.specific.planning.page.title" : "Plannings managment",
    "appms-ui.specific.planning.page.filter.center" : "Enrollment Center",
    "appms-ui.specific.planning.page.filter.name" : "Planning Name",
    "appms-ui.specific.planning.page.filter.start.date" : "Start Date",
    "appms-ui.specific.planning.page.filter.end.date" : "End Date",
    "appms-ui.specific.planning.page.filter.status" : "Status",
    "appms-ui.specific.planning.page.filter.btn" : "FILTER",

    "appms-ui.specific.planning.page.table.col1" : "Enrollment Center",
    "appms-ui.specific.planning.page.table.col2" : "Planning Name",
    "appms-ui.specific.planning.page.table.col3" : "Start Date",
    "appms-ui.specific.planning.page.table.col4" : "End Date",
    "appms-ui.specific.planning.page.table.col5" : "Status",
    "appms-ui.specific.planning.page.table.btn1" : "Planning overview",
    "appms-ui.specific.planning.page.table.btn2" : "Edit the planning",

    "appms-ui.preview.global.planning.title" : "Planning overview",
    "appms-ui.preview.global.planning.vip" : "VIP",
    "appms-ui.preview.global.planning.capacity" : "Capacity normal :",
    "appms-ui.preview.global.planning.capacity.vip" : "Capacity VIP :",

    "appms-ui-appointment.lbl.select.date" : "Select a date :",
    "appms-ui-appointment.lbl.select.hour" : "Select an hour :",
    
    "appms-ui.planning.title.create" : "Create a new planning",
    "appms-ui.planning.title.edit" : "Edit the planning",
    "appms-ui.planning.with.timeslot" : "Planning with time slot",
    "appms-ui.planning.form.name" : "Planning name",
    "appms-ui.planning.form.start.date" : "Start date",
    "appms-ui.planning.form.end.date" : "End date",
    "appms-ui.planning.form.start.morning.hour" : "Start hour morning",
    "appms-ui.planning.form.end.morning.hour" : "End hour morning",
    "appms-ui.planning.form.capacity" : "Capacity",
    "appms-ui.planning.form.start.afternoon.hour" : "Start hour afternoon",
    "appms-ui.planning.form.end.afternoon.hour" : "End hour afternoon",
    "appms-ui.planning.form.capacity.vip" : "Capacity VIP",
    "appms-ui.planning.form.duration" : "Duration (minutes)",
    "appms-ui.planning.form.btn.create" : "CREATE PLANNING",
    "appms-ui.planning.form.btn.reset" : "RESET",
    "appms-ui.planning.form.btn.update" : " UPDATE",

    "appms-ui.holiday.page.global.title" : "Management of holidays and weekends",
    "appms-ui.holiday.page.global.config.title" : "Global configuration",
    "appms-ui.holiday.page.global.days.monday" : "Monday",
    "appms-ui.holiday.page.global.days.tuesday" : "Tuesday",
    "appms-ui.holiday.page.global.days.wednesday" : "Wednesday",
    "appms-ui.holiday.page.global.days.thursday" : "Thursday",
    "appms-ui.holiday.page.global.days.friday" : "Friday",
    "appms-ui.holiday.page.global.days.saturday" : "Saturday",
    "appms-ui.holiday.page.global.days.sunday" : "Sunday",

    "appms-ui.holiday.page.global.filtre.name" : "Name",
    "appms-ui.holiday.page.global.filtre.start.date" : "Start Date",
    "appms-ui.holiday.page.global.filtre.end.date" : "End Date",
    "appms-ui.holiday.page.global.filtre.comment" : "Comment",
    "appms-ui.holiday.page.global.filtre.btn" : "FILTER",

    "appms-ui.holiday.page.global.table.title" : "Holidays",
    "appms-ui.holiday.page.global.table.new" : "Add a holiday",
    "appms-ui.holiday.page.global.table.col1" : "Name",
    "appms-ui.holiday.page.global.table.col2" : "Start Date",
    "appms-ui.holiday.page.global.table.col3" : "End Date",
    "appms-ui.holiday.page.global.table.col4" : "Comment",
    "appms-ui.holiday.page.global.table.delete" : "Delete this holiday",
    "appms-ui.holiday.select.label" : "Select",

    "appms-ui.holiday.create.new" : "Create a holiday",
    "appms-ui.holiday.create.form.nom" : "Name",
    "appms-ui.holiday.create.form.start.date" : "From",
    "appms-ui.holiday.create.form.end.date" : "To",
    "appms-ui.holiday.create.form.comment" : "Comment",
    "appms-ui.holiday.create.form.comment.max" : "60 characters maximum",
    "appms-ui.holiday.create.form.btn" : " CREATE HOLIDAY",

    "appms-ui.holiday.page.specific.title" : "Management of holidays and weekends",
    "appms-ui.holiday.page.specific.center" : "Select an enrollment center",
    "appms-ui.holiday.page.specific.days.monday" : "Monday",
    "appms-ui.holiday.page.specific.days.tuesday" : "Tuesday",
    "appms-ui.holiday.page.specific.days.wednesday" : "Wednseday",
    "appms-ui.holiday.page.specific.days.thursday" : "Thursday",
    "appms-ui.holiday.page.specific.days.friday" : "Friday",
    "appms-ui.holiday.page.specific.days.saturday" : "Saturday",
    "appms-ui.holiday.page.specific.days.sunday" : "Sunday",

    "appms-ui.holiday.page.specific.ref.global" : "Global weekends",

    "appms-ui.holiday.page.specific.filtre.name" : "Name",
    "appms-ui.holiday.page.specific.filtre.start.date" : "Start Date",
    "appms-ui.holiday.page.specific.filtre.end.date" : "End Date",
    "appms-ui.holiday.page.specific.filtre.comment" : "Comment",
    "appms-ui.holiday.page.specific.filtre.btn" : "FILTER",

    "appms-ui.holiday.page.specific.table.title" : "Holidays",
    "appms-ui.holiday.page.specific.table.new" : "Add a holiday",
    "appms-ui.holiday.page.specific.table.preview" : "Preview",
    "appms-ui.holiday.page.specific.table.col1" : "Name",
    "appms-ui.holiday.page.specific.table.col2" : "Start Date",
    "appms-ui.holiday.page.specific.table.col3" : "End Date",
    "appms-ui.holiday.page.specific.table.col4" : "Comment",
    "appms-ui.holiday.page.specific.table.delete" : "Delete this holiday",

    "appms-ui.holiday.page.preview.table.col1" : "Name",
    "appms-ui.holiday.page.preview.table.col2" : "Start Date",
    "appms-ui.holiday.page.preview.table.col3" : "End Date",
    "appms-ui.holiday.page.preview.table.col4" : "Comment",

    "appms-ui.holiday.page.preview.filtre.name" : "Name",
    "appms-ui.holiday.page.preview.filtre.start.date" : "Start Date",
    "appms-ui.holiday.page.preview.filtre.end.date" : "End Date",
    "appms-ui.holiday.page.preview.filtre.comment" : "Comment",
    "appms-ui.holiday.page.preview.filtre.btn" : "FILTER",

    "appms-ui.constraint.page.create.title" : "Add new constraint",
    "appms-ui.constraint.page.list.title" : "List of constraint",
    "appms-ui.constraint.page.table.col1" : "Constraint Name",
    "appms-ui.constraint.page.table.col2" : "Start Date",
    "appms-ui.constraint.page.table.col3" : "End Date",
    "appms-ui.constraint.page.table.col4" : "Start Hour",
    "appms-ui.constraint.page.table.col5" : "End Hour",
    "appms-ui.constraint.page.table.col6" : "Enrollment Center",
    "appms-ui.constraint.page.table.col7" : "List of appointment",
    "appms-ui.constraint.page.table.delete" :"Delete the constraint",

    "appms-ui.constraint.create.title" : "Add new constraint",
    "appms-ui.constraint.create.form.center" : "Enrollment Center",
    "appms-ui.constraint.create.form.name" : "Constraint Name",
    "appms-ui.constraint.create.form.start.date" : "Start Date",
    "appms-ui.constraint.create.form.end.date" : "End Date",
    "appms-ui.constraint.create.form.start.hour" : "Start Hour",
    "appms-ui.constraint.create.form.end.hour" : "End Hour",
    "appms-ui.constraint.create.form.btn" : " CREATE CONSTRAINT",
    "appms-ui.constrain.created.success" : "Constraint created with success.",
    "appms-ui.constraint.global.filtre" : "Global filter",
    "appms-ui.constrain.overlaps" : "This constraint overlaps with the following appointments : ",


    "appms-ui.modal.ok":"Yes",
    "appms-ui.modal.cancel":"Cancel",
    "appms-ui.planning.modal.confirme.delete.msg" : "Are you sure you want to delete this planning ?",
    "appms-ui.planning.modal.confirme.publish.msg" : "Are you sure you want to publish this planning ?",
    "appms-ui.planning.modal.confirme.unpublish.msg" : "Are you sure you want to disable this planning ?",
    "appms-ui.planning.modal.success.delete.msg" : "Planning successfully deleted.",
    "appms-ui.global.planning..status.enabled" : "Enabled",
    "appms-ui.global.planning..status.disabled" : "Disabled",
    "appms-ui.modal.planning.created.success":"Planning successfully created.",
    "appms-ui.modal.planning.updated.success":"Planning successfully updated.",

    "appms-ui.planning.form.invalid.name.1" : "The name must have a maximum of 64 characters.",
    "appms-ui.planning.form.invalid.name.2" : "The name must be at least 1 characters long.",
    "appms-ui.planning.form.invalid.start.date" : "The start date is mandatory.",
    "appms-ui.planning.form.invalid.end.date" : "The end date is mandatory.",
    "appms-ui.planning.form.invalid.end.date.after.start.date" : "The end date must be later than the start date.",
    "appms-ui.planning.form.invalid.start.morning.time" : "Morning start time is required.",
    "appms-ui.planning.form.invalid.end.morning.time" : "End of morning time is required.",
    "appms-ui.planning.form.invalid.end.morning.after.start.morning.time" : "The end of morning time must be after the start of morning time.",
    "appms-ui.planning.form.invalid.capacity":"Capacity is required.",
    "appms-ui.planning.form.invalid.capacity.upper.zero":"The capacity must be greater than or equal to 0.",
    "appms-ui.planning.form.invalid.start.afternoon.time" : "The afternoon start time is required.",
    "appms-ui.planning.form.invalid.end.date.afternoon.after.morning.end.date" : "The start time of the afternoon must be after the end time of the morning.",
    "appms-ui.planning.form.invalid.end.date.afternoon.start.date" : "The start time of the afternoon must be earlier than the end time of the afternoon.",
    "appms-ui.planning.form.invalid.end.afternoon.time" : "The late afternoon time is required.",
    "appms-ui.planning.form.invalid.capacity.vip":"VIP capacity is required.",
    "appms-ui.planning.form.invalid.capacity.vip.upper.zero":"The VIP capacity must be greater than or equal to 0.",
    "appms-ui.planning.form.invalid.duration":"Duration is required.",
    "appms-ui.planning.form.invalid.duration.upper.zero":"The duration must be greater than 0.",
    "appms-ui.planning.form.hint.reference" : "Reference : ",
    "appms-ui.appointment.select.center" : "Select enrollement center ",
    "appms-ui.planning.form.max.value" : "The maximum value for this field is 999.",
    "appms-ui.planning.form.max.duration.value" : "The maximum value for this field is 240 minutes.",

    "appms-ui.appointment.btn.save" : "Save",
    "appms-ui.appointment.success.save" : "Appointment saved with success :  ",
    "appms-ui.appointment.unavailable.date" : ".",
    "appms-ui.appointment.unavailable.label" : "There are no available slots at the moment for : ",
    "appms-ui.appointment.timeslot.capacity" : "Capacity : ",

    "appms-ui.constraint.form.invalid.name.1" : "The name must have a maximum of 64 characters.",
    "appms-ui.constraint.form.invalid.name.2" : "The name is required.",
    "appms-ui.constraint.form.invalid.start.date" : "Start date is required.",
    "appms-ui.constraint.form.invalid.end.date" : "End date is required.",
    "appms-ui.constraint.form.invalid.start.time" : "Start time is required.",
    "appms-ui.constraint.form.invalid.end.time" : "End time is required.",
    "appms-ui.constraint.form.invalid.end.date.after.start.date" : "The end date must be later than the start date.",
    "appms-ui.constraint.form.invalid.end.time.after.start.time" : "The end time must be after the start time of the constraint.",
    "appms-ui.constraint.form.invalid.center.name" : "The enrollment center is required.",
    "appms-ui.constraint.form.invalid.option.center.name" : "The value must match the available options.",
    "appms-ui-constrainte.delete.confirme" : "Are you sure you want to remove this constraint ?",
    "appms-ui.constraint.delete.success" : "Constraint successfully removed.",

    "appms-ui.holiday.confirme.delete" : "Are you sure you want to delete the holiday?",
    "appms-ui.holiday.delete.success" : "Holiday successfully deleted.",
    "appms-ui.holiday.form.invalid.name.1" : "The name must have a maximum of 64 characters.",
    "appms-ui.holiday.form.invalid.name.2" : "The name is required.",
    "appms-ui.holiday.form.invalid.start.date" : "The start date is required.",
    "appms-ui.holiday.form.invalid.end.date" : "The end date is required.",
    "appms-ui.holiday.form.invalid.end.date.after.start.date" : "The end date must be later than the start date.",
    "appms-ui.holiday.form.invalid.coment.1" : "The comment must have a maximum of 64 characters.",
    "appms-ui.holiday.form.invalid.coment.2" : "The comment is required.",
    "appms-ui.holiday.form.success.submit" : "Holiday created with success.",

    "appms-ui.settings.title" : "Global setting",
    "appms-ui.settings..filtre.title" : "Search",
    "appms-ui.setting.table.col1" : "Code enrollement center",
    "appms-ui.setting.table.col2" : "Enrollement center",
    "appms-ui.setting.table.col3" : "Enable appointment service",
    "appms-ui.setting.table.appt.enabled" : "Enable",
    "appms-ui.setting.table.appt.disabled" : "Disable",
    "appms-ui.filtre.status.all" : "All",
    "appms-ui.filtre.status.enable" : "Enabled",
    "appms-ui.filtre.status.disable" : "Disabled",
    ...
}
Translation file

By looking at the translation file, I determined the appointment dates, the constraints, and everything else is set by an admin.

Anyways, then there came an important endpoint that directly related to the UI element. The eservices endpoint returned the types of services provided through the website.

HTTP
GET /iups-api/eservices

[
    {
        "id": 1,
        "name": "First Issuance",
        "nepaliName": "पहिलो पटक राहदानी लिन",
        "code": "PP_FIRSTISSUANCE",
        "enabled": true,
        "requiresAuthentication": false,
        "requiresPayment": false,
        "createRequest": true,
        "description": "Passport First Issuance",
        "category": "PASSPORT",
        "order": 0,
        "version": 0,
        "docTypes": null
    },
    {
        "id": 2,
        "name": "Renewal",
        "nepaliName": "म्याद सकिएको  राहदानी नवीकरण गर्न",
        "code": "PP_RENEWAL",
        "enabled": true,
        "requiresAuthentication": false,
        "requiresPayment": false,
        "createRequest": true,
        "description": "Passport Renewal",
        "category": "PASSPORT",
        "order": 0,
        "version": 0,
        "docTypes": null
    },
    {
        "id": 3,
        "name": "Replacement (Lost/Stolen)",
        "nepaliName": "हराएको/चोरिएको राहदानीको सट्टामा",
        "code": "PP_REPLACEMENT_LOST_STOLEN",
        "enabled": true,
        "requiresAuthentication": false,
        "requiresPayment": false,
        "createRequest": true,
        "description": "Passport Replacement (Lost/Stolen)",
        "category": "PASSPORT",
        "order": 0,
        "version": 0,
        "docTypes": null
    },
    {
        "id": 4,
        "name": "Replacement (Damaged)",
        "nepaliName": "बिग्रेको राहदानीको सट्टामा",
        "code": "PP_REPLACEMENT_DAMAGED",
        "enabled": true,
        "requiresAuthentication": false,
        "requiresPayment": false,
        "createRequest": true,
        "description": "Replacement (Damaged)",
        "category": "PASSPORT",
        "order": 0,
        "version": 0,
        "docTypes": null
    }
]
Type of form

In my case, I needed the option with id 1.

Second step

Upon selecting the desired service, the subsequent step in the form entails choosing an appointment date, which holds paramount importance. Notably, before reaching the appointment date selection, we encounter the option to choose between a 34-page or 66-page passport. Simultaneously, the system downloads crucial information comprising the appointment location details and corresponding codes, which are essential for the appointment process. This information download ensures that we have all the necessary data readily available when scheduling the appointment.

Selecting passport type, normal selection is 34 pages

The geographical information looked as follows.

HTTP
GET /iups-api/masterdata/resources/GEOGROUP

[
    ...,
    {
        "id": 263,
        "code": "GDK",
        "name": "Gandaki",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Gandaki",
        "locations": null
    },
    {
        "id": 264,
        "code": "LMB",
        "name": "Lumbini",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Lumbini",
        "locations": null
    },
    {
        "id": 265,
        "code": "PVT",
        "name": "Madhesh",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Madhesh",
        "locations": null
    },
    {
        "id": 266,
        "code": "KRN",
        "name": "Karnali",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Karnali",
        "locations": null
    },
    {
        "id": 267,
        "code": "SDP",
        "name": "Sudurpashchim",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Sudurpashchim",
        "locations": null
    },
    {
        "id": 268,
        "code": "BGM",
        "name": "Bagmati",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Bagmati",
        "locations": null
    },
    {
        "id": 269,
        "code": "PVO",
        "name": "Province No. 1",
        "level": "2",
        "codeParent": "NPL",
        "enabled": true,
        "description": "Province No. 1",
        "locations": null
    },
    {
        "id": 301,
        "code": "LTP",
        "name": "Lalitpur",
        "level": "3",
        "codeParent": "BGM",
        "enabled": true,
        "description": "Lalitpur",
        "locations": null
    },
    {
        "id": 304,
        "code": "KTM",
        "name": "Kathmandu",
        "level": "3",
        "codeParent": "BGM",
        "enabled": true,
        "description": "Kathmandu",
        "locations": null
    },
    ...
]
Geographical information

Third step onwards

Since the required information was already present, there was no need for additional requests except for the captcha verification. Once a district was chosen, the system promptly downloaded the final appointment locations. It is essential to adhere to the rule that the appointment process is exclusively permitted at either the Department of Passport in Kathmandu or the District/Area Administration Office of the individual’s respective home district. This restriction ensures that individuals can only proceed with the appointment process at these designated locations, thereby simplifying the overall procedure.

Appointment form

The two requests were as follows.

HTTP
GET /iups-api/masterdata/geogroups/304/locations
// 304 is the ID of Kathmandu as seen in the previous response
[
    {
        "id": 78,
        "code": "KTM",
        "name": "DAO Kathmandu",
        "address": "DAO Kathmandu",
        "enabled": true,
        "description": "DAO Kathmandu"
    },
    {
        "id": 79,
        "code": "DOP",
        "name": "Department of Passports",
        "address": "Department of Passports",
        "enabled": true,
        "description": "Department of Passports"
    }
]
Location response
HTTP
POST /iups-api/locations/codes
["KTM","DOP"]

[
    {
        "id": 78,
        "name": "DAO Kathmandu",
        "code": "KTM",
        "organization": "Kathmandu",
        "active": true,
        "appointmentFeatureStatus": true
    },
    {
        "id": 79,
        "name": "Department of Passports",
        "code": "DOP",
        "organization": "DoP, Kathmandu",
        "active": true,
        "appointmentFeatureStatus": true
    }
]
Code POST Request

Once we select a location, we then get the timeslots as follows.

HTTP
GET /iups-api/calendars/79/false
// 79 is the ID of Department of Passports

{
    "minDate": "2023-06-27",
    "maxDate": "2023-07-02",
    "offDates": [
        "2023-06-29",
        "2023-06-28",
        "2023-06-27",
        "2023-07-01"
    ],
    "weeklyOffDaysIndexes": [
        6
    ]
}
Appointment slots

The server response includes important fields that influence the date selection for appointment slots. The “minDate” and “maxDate” fields establish the range of available dates for scheduling appointments. Additionally, the “offDates” field specifies the dates on which the slots have already been filled or are otherwise unavailable. Moreover, the “weeklyOffDaysIndexes” field indicates the index of the public holiday, with Saturday typically represented as 6 in Nepal, assuming Sunday is represented as 0.

It is noteworthy that the server response does not currently include a time slot component. The availability of specific time slots will become visible only after selecting a date for the appointment.

Timeslots are full
HTTP
GET /iups-api/timeslots/79/2023-06-30/false
// 79 is DoP and the appointment for the date

[
    {
        "name": "10:00",
        "status": false,
        "capacity": 0,
        "vipCapacity": 0
    },
    {
        "name": "10:30",
        "status": false,
        "capacity": 0,
        "vipCapacity": 0
    },
    {
        "name": "11:00",
        "status": false,
        "capacity": 0,
        "vipCapacity": 0
    },
    {
        "name": "11:30",
        "status": false,
        "capacity": 0,
        "vipCapacity": 0
    },
    {
        "name": "12:00",
        "status": false,
        "capacity": 0,
        "vipCapacity": 0
    },
    {
        "name": "12:30",
        "status": false,
        "capacity": 0,
        "vipCapacity": 0
    },
    {
        "name": "13:00",
        "status": true,
        "capacity": 9,
        "vipCapacity": 0
    },
    {
        "name": "13:30",
        "status": true,
        "capacity": 43,
        "vipCapacity": 0
    },
    {
        "name": "14:00",
        "status": true,
        "capacity": 44,
        "vipCapacity": 0
    },
    {
        "name": "14:30",
        "status": true,
        "capacity": 44,
        "vipCapacity": 0
    }
]
Time slots

The Problem

During my previous experience a year ago, I encountered a situation where, by the time the server load decreased, the appointment capacity would already reach its maximum limit of zero availability. Assuming there were still available slots, upon selecting a date, the subsequent step involved filling out another form where I would input my name. Following this, the form would undergo a validation process, with separate requests returning the necessary validation for each field. This allowed me to fill out the form accurately before submitting it to complete the appointment process.

The Solution

As discussed earlier, while exploring the website, I deliberately chose a location with minimal service seekers to gain insights into its underlying request structure. Upon careful examination, it became apparent that the majority of these requests fell into the static category. These requests primarily encompassed static information such as location details, codes, field validation, configuration, and available time slots. Remarkably, these static requests remained consistent and unchanged throughout the process. The only variable element was the captcha, which required periodic updates.

On the other hand, there were certain dynamic actions that necessitated communication with the server, such as confirming slots, uploading documents, and validating forms. Interestingly, unlike the static requests, these dynamic POST requests did not trigger server error popups or force website reloads. They seamlessly facilitated the necessary interactions with the server, ensuring a smooth and uninterrupted process.

Given the prevalence of static requests and their potential to cause disruptions, an idea came to mind. Why not take control of serving the static responses myself by utilizing Cypress request stubbing? This approach would significantly reduce the number of requests to the server, minimizing potential issues and enhancing the overall user experience. Moreover, it is worth noting that these POST requests, unlike the static ones, did not lead to server error popups or compel website reloads, adding to the efficiency of the process.

The Process

So, I initialized a cypress project with a lot of stub code as follows.

JavaScript
describe("Pre-enrollment", () => {
  beforeEach(() => {
    cy.intercept("/iups-api/configuration/module/request**", {
      fixture: "request.json",
    });
    cy.intercept("/iups-api/masterdata/resources/nationality**", {
      fixture: "nationality.json",
    });
    cy.intercept("/iups-api/locations**", {
      fixture: "locations.json",
    });
    cy.intercept("/iups-api/configuration/module/services**", {
      fixture: "services.json",
    });
    cy.intercept("/iups-api/configuration/module/binary.document**", {
      fixture: "binary-document.json",
    });
    cy.intercept("/iups-api/eservices**", {
      fixture: "eservices.json",
    });
    cy.intercept("**/appms-i18n/en.json**", { fixture: "appms-en.json" });
    cy.intercept("**/i18n/en.json**", { fixture: "en.json" });
    cy.intercept("**/version**", { fixture: "version.json" });
    cy.intercept("**/main.js**", { middleware: true }, (req) => {
      req.on("before:response", (res) => {
        res.headers["cache-control"] = "max-age=604800";
      });
    });
    cy.intercept("**/main-es2015**.js**", { middleware: true }, (req) => {
      req.on("before:response", (res) => {
        res.headers["cache-control"] = "max-age=604800";
      });
    });
    cy.intercept("**/scripts.**.js**", { middleware: true }, (req) => {
      req.on("before:response", (res) => {
        res.headers["cache-control"] = "max-age=604800";
      });
    });
    cy.intercept("/iups-api/eservices/PP_FIRSTISSUANCE**", {
      fixture: "pp-first.json",
    });
    cy.intercept("iups-api/eservices/PP_RENEWAL**", {
      fixture: "pp-renewal.json",
    });
    cy.intercept("/iups-api/masterdata/resources/GEOGROUP**", {
      fixture: "geogroup.json",
    });
    cy.intercept("/iups-api/masterdata/geogroups/304/locations**", {
      fixture: "ktm-locations.json",
    });
    cy.intercept("/iups-api/masterdata/geogroups/281/locations**", {
      fixture: "kailali-locations.json",
    });
    cy.intercept("/iups-api/locations/codes", { fixture: "codes.json" });
    cy.intercept("/iups-api/calendars/**/false", {
      fixture: "calendars.json",
    });
    cy.intercept("/iups-api/timeslots/**/false**", {
      fixture: "timeslots.json",
    });
    // cy.intercept("/iups-api/appointments**", { fixture: "appointments.json" });
    cy.intercept(
      "/iups-api/eservices/PP_FIRSTISSUANCE/documentTypes/PP/fieldvalidations**",
      { fixture: "fieldvalidations.json" }
    );
    cy.intercept(
      "/iups-api/eservices/PP_RENEWAL/documentTypes/PP/fieldvalidations**",
      {
        fixture: "pp-renewal-fieldvalidations.json",
      }
    );
    cy.intercept(
      "/iups-api/eservices/PP_FIRSTISSUANCE/documentTypes/PP/supportingdocuments**",
      { fixture: "supportingdocs.json" }
    );
    cy.intercept(
      "/iups-api/eservices/PP_RENEWAL/documentTypes/PP/supportingdocuments**",
      {
        fixture: "pp-renewal-supportingdocs.json",
      }
    );
    cy.intercept("/iups-api/configuration/I5/media**", (req) => {
      req.continue((res) => {
        res.send(true);
      });
    });
    cy.intercept("/null", (req) => {
      req.continue((res) => {
        res.send(true);
      });
    });
  });
  it("apply form", () => {
    cy.visit("/request-form");
  });
});
cypress spec file

After capturing the responses, I proceeded to copy them into their respective fixture files. Furthermore, I would also put a high number in capacity field and confirm as soon as possible during the test execution. This way, when running the test, the responses were fetched from my fixture files instead of directly from the server. By utilizing the fixture files, I could simulate the server’s responses.

Caveats

While I successfully tackled the primary challenge of filling out the passport form, it was important to acknowledge that the solution did not provide complete immunity against the existing problem. Although the probability of encountering issues significantly decreased, there remained a slim chance of experiencing them. Additionally, there was a time restriction imposed, allowing only 10-15 minutes to complete the form.

To navigate the requests that required communication with the server, I implemented a retry mechanism due to potential failures caused by server load. This meant manually retrying the requests that failed initially, such as document uploads, and dealing with occasional issues like captcha loading. Undoubtedly, this retry process resulted in some time loss during the overall form completion procedure.

Despite these limitations and the additional time invested in retries, the approach I adopted significantly mitigated the risks and improved the likelihood of successfully submitting the passport form within the given timeframe. It required perseverance and adaptability to handle the challenges posed by the server load, ensuring a smoother and more efficient form filling experience.

Conclusion

In conclusion, the journey of overcoming Nepal’s Passport Form challenges with Cypress has been a transformative experience. By leveraging the power of Cypress request stubbing and a strategic approach, I was able to navigate the intricacies of the passport application process with greater ease and efficiency. The insights gained from analyzing the requests and utilizing the stubbing mechanism allowed me to overcome the hurdles posed by server load and minimize the chances of encountering errors.

However, it is crucial to acknowledge the failure of the government in providing a seamless and user-friendly service. The existing issues, such as server errors, frequent reloads, and limited time constraints, highlight the need for improvements in the online passport application system. While my approach significantly mitigated these challenges, it ultimately served as a workaround rather than a permanent solution. It is imperative for the government to invest in enhancing their digital infrastructure, optimizing server capacity, and addressing the limitations that hinder a smooth application process.

Furthermore, through the application of this technique, I extended my assistance to not only myself but also to my friends, family members, and their families. By sharing my knowledge and utilizing Cypress request stubbing, I was able to facilitate the form filling process for a broader circle, contributing to a collective success.

In conclusion, while the passport application journey in Nepal may still have room for improvement, the use of Cypress and the strategies employed in this exploration have proven to be valuable tools for navigating the challenges and increasing the chances of a successful application. It is my hope that this firsthand account and the insights shared will inspire further discussions, innovation, and ultimately drive positive changes in Nepal’s passport application system.

Also read: Using flagsmith for remote configuration and feature flagging

Read about cypress from here


5 5 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments