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


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 pdf's 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 change the links in the VS code.  The debugger was useful as there were many different types of links within 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!");
    }
}



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.

Tuesday, 3 September 2024

Power Automate Flows naming for documentation

I was recently shown this format and I think it's fantastic.  I'm moving all my flows to this Action naming approach:


Format: <Original OOTB name of the Action> | <Verb on Noun> 

Also, see my Flow tips here