Friday, 8 November 2024

Playwright series - Post 2 - Refactored TS code for Consciously verify Apps in Production

Overview: Create a function with Playwright tests that loops through all my production apps, logs in, and validates the Page title load on each app's home page.

Steps:

1. Create the spec.ts code that reads app.json to loop thru and validate sites

2. Record and Store the session state for all future test runs (Used for MFA in the tests runs)

3. Create an apps.json file containing URLs to open and validate


4. After running the test, you'll see that the 3 tests were completed successfully. In my case, there were 2 Power Apps with MFA enabled and an anonymous public website that had been checked.

Optional
Create short cuts to run your tests using PowerShell
PS C:\repos\PW> npx playwright test -g "Prod-CanvasApps" --project=chromium --headed

Next Steps:

Run continuously using the Azure Playwright Service.

=========================

Playwright Series

Playwright Post 1 - Overview of E2E testing using Playwright

Playwright Post 2 - Continuously Test/Monitor Canvas apps and website with MFA enabled (this post)

Playwright Post 3 - Add App Insights logging inside your Playwright tests 

Playwright Post 4 - 6 Min walkthru of Playwright testing with Azure Monitor

Playwright Post 5 - Understanding how playwright works

Playwright Post 6 - Unattended testing when secured with MFA 


Other Posts

Upgrading two C# Blazor web applications and verifying using Playwright - super fast 

Mendix - Part 2 - Diving deeper (E2E automation testing of Mendix using Playwright)

Low-code testing with playwright walkthru

Continiously Monitor Apps using Playwright with TS

Testing Canvasa apps with Playwright using C# (rather use TS, it's better)

Wednesday, 6 November 2024

Fix links within Pdf files when moving from a File share to web hosting

Overview: Build a console to help migrate more than 80k PDF document internal links. The client used a DFS SMB file share to hold index PDFs and multiple documents that needed to be moved to a SharePoint document library.

Hypothesis: Loop through all PDFs in a folder; if there are links, identify the file server links and convert them to web links so they work in the new SharePoint document library.  Various tools were identified as possible solutions, but came up short in the migration.  Two good tools are Replace Magic & PDF-XChange Editor.

Resolution: Below is the C# code I wrote to update the links in VS Code.  The debugger was helpful because there were many different link types across the plethora of PDFs.


C# Code

using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Annot;
using iText.Kernel.Pdf.Action;
using iText.Bouncycastle.Crypto;  // pdf fails at runtime periodically without the directive
using System.Text.RegularExpressions;
class Program
{
    static void Main(string[] args)
    {
        string folderPath = @"C:\Users\PaulBeck\Downloads\Software\LinkConvert\ConvertLinksCsharp\"; // Replace with your folder path          
        Console.WriteLine("Please enter a folder Path: e.g. " + folderPath);
        string inputPath = Console.ReadLine();        
        Console.WriteLine("Last Path: e.g. Childfolder2");
        string inputPathVol = Console.ReadLine();
        if (inputPath.Length>5)  {
            folderPath = inputPath;   }
        string[] pdfFiles = Directory.GetFiles(folderPath, "*.pdf");
        foreach (string file in pdfFiles)
        {
            Uri fileUri = new Uri(file);
            string directory = Path.GetDirectoryName(file);
            string filename = Path.GetFileNameWithoutExtension(file);
            string extension = Path.GetExtension(file);           
            string newFilename = $"{filename}_new{extension}";  // Create the new filename
            string newFilePath = Path.Combine(directory, newFilename); // Combine the directory and new filename to form the new URL
            UpdatePdf(fileUri.AbsoluteUri,newFilePath, inputPathVol);
        }
    }
private static void UpdatePdf(string inputFilePath, string outputFilePath, string lastPathPart)
{
    var varInnerName = "";
    PdfDocument pdfDoc = new PdfDocument(new PdfReader(inputFilePath), new PdfWriter(outputFilePath));
        for (int i = 1; i <= pdfDoc.GetNumberOfPages(); i++)        // Iterate through the pages
        {
            var page = pdfDoc.GetPage(i);
            var annotations = page.GetAnnotations();
            foreach (var annotation in annotations)            // Iterate through the annotations
            {
                if (annotation.GetSubtype().Equals(PdfName.Link))
                {
                    var linkAnnotation = (PdfLinkAnnotation)annotation;
                    var action = linkAnnotation.GetAction();                                                        
                    if (action is PdfDictionary dictionary)
                    {
                        foreach (var key in dictionary.KeySet())
                        {
                            var value = dictionary.Get(key);
                            Console.WriteLine($"{key}: {value}");
                            var varPdfNameF = dictionary.Get(PdfName.F);
                            if (varPdfNameF is PdfDictionary varF2Dict)      {
                                varInnerName = varF2Dict.Get(PdfName.F).ToString();  }
                        }
                    }
                    else
                    {   Console.WriteLine("No URL found.");    }  
if (action != null && (action.Get(PdfName.S).Equals(PdfName.GoToR) || action.Get(PdfName.S).Equals(PdfName.Launch)))                
                      {
                        var varF = action.GetAsString(PdfName.F)?.ToString() ?? "";
                        var uri = $"https://radimaging.sharepoint.com/sites/Documents/Standards/{lastPathPart}/{varInnerName}";          
                       string pattern = @"\.\./";
                       if(uri.Contains("../"))    // string cleanUrl1 = Regex.Replace(uri, pattern, string.Empty);
                       {          
                        uri = $"https://radimaging.sharepoint.com/sites/Documents/Standards/{varInnerName}";
                        uri= Regex.Replace(uri, pattern, string.Empty);                       
                       }
                        var newAction = PdfAction.CreateURI(uri);
                        if (varF.Length > 20)      {
                            newAction = PdfAction.CreateURI(varF);     }
                        linkAnnotation.SetAction(newAction);
                    }           
                }
            }
        }
        pdfDoc.Close();
        Console.WriteLine("PDF links updated successfully!");
    }
}


Quick way to use the PDF X-Change Editor tool to change links.

Example of how to correctly set internal links within a pdf:


Example of how to set links to pages or files on the Internet/extranet:


Update Nov 2025: User Playwright MCP to download an index pdf, and then download or open files and pages to validate the links worked.  Updated the index pdf using C# Console built using Github Copilot (GHCP) and updated into SharePoint using MS Graph the updated fixed pdf.

Sunday, 13 October 2024

Document Retention: 6 years is always a solid guess

Overview: Documents need to be retained for specific periods and disposed of at a certain point. 

  1. PAYE Records: 3 years after the end of the tax year to which they relate
  2. Banking records: 6 years
  3. Intellectual Property: 6 years
  4. Donations: 3 or 6 years
  5. Employment Records: Generally, 6 years
  6. Health & Safety Policy: The life of the company and historic versions, previous versions  should be kept for a minimum of ten years
  7. Most other Health and Safety Documents: Retain for 6 years if there are updates.
  8. Accidents and injuries: at least 3 years from the date of reporting
  9. Pension scheme records:  life of Pension scheme +
  10. Hazardous waste: 3 years
  11. Data Protection Act (personal data): Delete as soon as possible
  12. Company Documents

  • Key company documents: Articles of Association, certificate of incorporation, memorandum of association, stakeholder official documents,  ...:  retained for the life of the company. 
  • Directors' contracts & Directors’  qualifying indemnity provisions:  six years after termination or expiry.
  • Annual Budgets, quarterly financial reports, VAT records, Statutory Report and Accounts:  6 years
  • Corporation Tax records: 7 years after filing
  • Contract for purchase of own shares, labour agreements: 10 years from the buying date
  • Property Records: 15 years after expiry
Note: This is my rough guide, as lots of people love talking about Document Retention.

Thursday, 10 October 2024

Network calls intermittently do not occur in Published play mode on a Canvas app

Problem: An app created does not run Patch or any other network call when published and in run mode. The network calls work in edit mode.  Looking at the logs, I identified some users are successfully updating using network calls

Figure 1. Monitoring shows the Edit/Play working and the Publish/Play not making any network calls.

Initial Hypothesis:

The Patch call works in edit mode, and then intermittently, some users get it to work in Published/Play mode.  This tells me the data in the Patch call is causing the network patch call to fail.

Using a Trace and running in play mode, I remove properties until I find the offending property.

Figure 2. In edit mode, a label control, CurrentDateVaue_1.Text is used

Once, we traced the value of CurrentDateValue_1, which is set by the global variable varCurrentDateTime. As this is done on a random screen, if the user does not open the screen in Publish /Play mode, the label control CurrentDateValue_1.Text is never set. 

Resolution: Replace all the code using CurrentDateValue_1.Text with the variable varCurrentDateTime. The label control is not set when the network/patch call is made, and bizarrely, it does not happen. I'd expect it to send regardless and let the error handling catch the issue.

Monday, 16 September 2024

Opening Canvas App in Edit mode takes excessive time to open

Problem: Two Canvas apps in my enterprise portfolio took over 20 minutes to load in edit mode.

Initial Hypothesis: If I specify the version of a studio in the URL to an old version, the offending app loads in edit mode in under a minute.

&studio-version=v3.24052.30.298708436 onto the Canvas app edit URL

This tells me that something in my app is offending when the latest studio versions are used to edit the app.  While this was okay for a few weeks as a workaround, MS support eventually gave me two settings that fixed both my apps.

Resolution

  • Turn off "Collection access in component scope"
  • Turn on "New analysis engine"

Monday, 9 September 2024

Microsoft Learn company training plans is worth a look

Overview: Microsoft Learn has a nice feature to ensure users are trained on MS technologies. You can create collections of learning resources and add these to plans. Then, specify who should do the training and record the progress.

An example of a plan for new joiners to ensure they are skilled.

Thoughts: I like this tool as you can create a set of courses for users to attend, and it tracks their completion/progress.  For instance, in a Centre of Excellence, users are expected to have completed some training before being given access to a Power Platform Dev environment.

I have not been able to add my own company's content, so I may have a recording of governance and standards for an environment that devs need to be aware of. I'd like the ability to add wikis or mp4s that users watch and then accept they have completed. It may be there, but I can't find it. Also, the portal's branding should allow enterprises to add their own logos. This would be a great Learning Management System (LMS) with customised content.