Forum Discussion

KRogersX's avatar
KRogersX
Contributor
3 months ago
Solved

How to Test If Associated PDF Viewer Has Been Opened?

Hey y'all,

We have a PDF user manual that can be accessed from the desktop application under test.

I need to test if the PDF has actually successfully opened in the associated PDF viewer. I don't want to assume the user has a specific PDF viewer like Adobe Reader. 

Any ideas on how to determine which PDF viewer is associated to PDF files, and if it's been opened successfully with a specific PDF?

 

  • In case it's helpful, here's what I came up with. This only works if the associated PDF app is initially opened with the PDF file; if the app is already open, it gets much harder to check. (Chrome and Edge in particular are difficult to check since it's highly likely they are already open.)

    import winreg
    import shlex
    
    class Registry:
    	@staticmethod
    	def ReadValue( root, keyPath, valueName ):
    		try:
    			with winreg.OpenKey( root, keyPath, 0, winreg.KEY_READ ) as key:
    				value, _ = winreg.QueryValueEx( key, valueName )
    				return value
    		except FileNotFoundError:
    			Log.Error( fr"Registry key or value not found: \{keyPath}\{valueName}" )
    		except Exception as e:
    			Log.Error( f"Exception: {e}" )
    
    		return
    
    	@staticmethod
    	def DoesProgIdKeyExist( progId ):
    		try:
    			return winreg.OpenKey( winreg.HKEY_CLASSES_ROOT, progId ) != ""
    		except FileNotFoundError:
    			Log.Error( f"ProgId '{progId}' key not found." )
    		except Exception as e:
    			Log.Error( f"Exception: {e}" )
    
    		return
    
    	@staticmethod
    	def GetAppOpenCommandPath( progIdKey ):
    		try:
    			keyValue = Registry.ReadValue( winreg.HKEY_CLASSES_ROOT, fr"{progIdKey}\shell\open\command", "" )
    			exePath = shlex.split( keyValue )[0]
    			return exePath
    		except FileNotFoundError:
    			Log.Error( f"App open command key not found for key '{progIdKey}'." )
    		except Exception as e:
    			Log.Error( f"Exception: {e}" )
    
    		return
    # end Registry class
    
    def GetProcessName( exePath ):
    	startIndex = exePath.rindex( '\\' ) + 1
    	endIndex = exePath.rindex( '.' )
    	processName = exePath[startIndex:endIndex]
    
    	return processName
    
    def CheckProcessForPdfFile( pdfProcessName, pdfFileName ):
    	result = False
    	processCount = Sys.ChildCount
    	for index in range( 0, processCount - 1 ):
    		process = Sys.Child( index )
    		if pdfProcessName == process.ProcessName:
    			#Log.Message( f"PDF process '{process.ProcessName}' found..." )
    			if pdfFileName.lower() in process.CommandLine.lower():
    				Log.Message( "PDF file in command line.")
    				result = True
    				break
    
    	return result
    
    def Example():
    	pdfExtPath = r"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice"
    	pdfExtValue = "ProgId"
    	progId = Registry.ReadValue(winreg.HKEY_CURRENT_USER, pdfExtPath, pdfExtValue)
    	Log.Message(f"Prog ID = {progId}")
    	
    	exePath = Registry.GetAppOpenCommandPath( progId ) if Registry.DoesProgIdKeyExist( progId ) else ""
    	Log.Message( f"App open command path = {exePath}")
    
    	pdfProcessName = GetProcessName( exePath )
    
    	thePdfFileName = "User-Manual.pdf"
    	if Sys.WaitProcess( pdfProcessName, 5000 ).Exists:
    		Log.Checkpoint( f"PDF viewer '{pdfProcessName}' is open." )
    		if CheckProcessForPdfFile( pdfProcessName, thePdfFileName ):
    			Log.Checkpoint( f"PDF viewer has opened the expected PDF file '{thePdfFileName}'." )
    		else:
    			Log.Warning( f"The expected PDF file '{thePdfFileName}' might be open, but can't determine for sure." ) 
    	else:
    		Log.Error( "PDF viewer is not open." )

     

11 Replies

  • rraghvani's avatar
    rraghvani
    Icon for Champion Level 3 rankChampion Level 3

    Check the registry key for .pdf extension to see what application is associated with it

    HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice

    The ProgId key will show the application e.g. FoxitPhantomPDF.Document

    If you then search for the ProgId (in this case it's "FoxitPhantomPDF.Document")  in the registry i.e.

    HKEY_CLASSES_ROOT\FoxitPhantomPDF.Document\shell\open\command

    You will see the full execution path name

    You can then use TestComplete' Process method to see if the application is running.

  • scot1967's avatar
    scot1967
    Icon for Champion Level 2 rankChampion Level 2

    Hi KRogersX,

    This may help on the registry bit...

    How To: Read data from the Windows Registry | SmartBear Community

    As for ..."if it's been opened successfully with a specific PDF?" You could check the object tree for the viewer process with some code after you retrieve the associated viewer.  It may take some clever code to check for the various viewers but it should be possible.  Opening via a browser would be a different process as well.  

    ... If you find my posts helpful drop me a like! 👍 Be sure to mark or post the solution to help others out and/or to credit the one who helped you. 😎

  • scot1967's avatar
    scot1967
    Icon for Champion Level 2 rankChampion Level 2

    This code should open the file in the specified path and you can use the registry code to determine the viewer to check for...

    function OpenAndVerifyPDF(pdfPath) {
      var shell = Sys.OleObject("WScript.Shell");
      shell.Run('"' + pdfPath + '"', 1, false); // Launch PDF with default viewer
    
      // Wait a few seconds for the viewer to open
      Delay(3000);
    
      // Check if the Adobe Reader or another viewer process is running
      if (Sys.WaitProcess("AcroRd32", 5000).Exists || Sys.WaitProcess("Acrobat", 5000).Exists) {
        Log.Message("PDF viewer opened successfully.");
        return true;
      } else {
        Log.Error("PDF viewer did not open.");
        return false;
      }
    }

    P.S. The bit of code in this example that checks the tree for the running process could be useful after your app has opened the file.  You will need to check the associated  process and window caption for the filename if available.

    • KRogersX's avatar
      KRogersX
      Contributor

      Thanks. The PDF is opened by the application under test, so it should already be open; I just need to make sure it's open. Looks like the second part after the Delay() shows how to do that...

    • scot1967's avatar
      scot1967
      Icon for Champion Level 2 rankChampion Level 2

      The tech article I referenced should help you find the reader process to substitute at line 9.  It sounds like you have it.  The reader should be known in your test environment. However, flexible tests are always good.  😉

  • After your application under test launches the required PDF file and to verify that the file is opened correctly in its associated application even when another instance of the viewer might already be running or in Browser / Embedded viewers.

    This can be achieved by reading the Windows Registry to determine the default PDF viewer, and checking window titles for the expected file name within a 10-second timeout.

    I have not tried it out but here's a TestComplete example in JavaScript:

    function VerifyPDF(pdfFilePath) {
      if (!aqFileSystem.Exists(pdfFilePath)) {
        Log.Error("The specified PDF file does not exist: " + pdfFilePath);
        return;
      }
    
      var fileName = aqFileSystem.GetFileName(pdfFilePath).toLowerCase();
      var fileBase = fileName.replace(/\.[^/.]+$/, "");
    
      // === STEP 1: Determine default viewer from registry
      var exeName = null;
      var useFallback = false;
    
      var progID = aqRegistry.GetRegValue("HKEY_CLASSES_ROOT\\.pdf", "");
      if (progID) {
        var commandPath = "HKEY_CLASSES_ROOT\\" + progID + "\\shell\\open\\command\\";
        var openCommand = aqRegistry.GetRegValue(commandPath, "");
        var exeMatch = openCommand.match(/"?(.*?\.exe)"/i);
        var exePath = exeMatch ? exeMatch[1] : null;
        exeName = exePath ? aqFileSystem.GetFileName(exePath).toLowerCase() : null;
      }
    
      // === STEP 2: Define fallback process list (common browsers and embedded containers)
      var fallbackProcesses = [
        "msedge", "chrome", "firefox", "iexplore", // browsers
        "electron", "yourapp", "customviewer" // embedded apps (customize here)
      ];
    
      if (!exeName) {
        Log.Warning("Could not detect default PDF viewer from registry. Falling back to known browser/embedded types.");
        useFallback = true;
      }
    
      var timeout = 10000; // 10 seconds
      var start = aqDateTime.GetTickCount();
      var found = false;
      var anyViewerFound = false;
    
      while ((aqDateTime.GetTickCount() - start) < timeout) {
        for (var i = 0; i < Sys.ProcessCount; i++) {
          var proc = Sys.Process(i);
          var procName = proc.Name.toLowerCase();
    
          // Match default viewer or fallback list
          var isTargetViewer = (!useFallback && procName === exeName.replace(".exe", "")) ||
                               (useFallback && fallbackProcesses.indexOf(procName) >= 0);
    
          if (!isTargetViewer) continue;
          anyViewerFound = true;
    
          // === Check top-level windows
          for (var j = 0; j < proc.WindowCount; j++) {
            var wnd = proc.Window("*", "*", j);
            if (!wnd.Exists) continue;
    
            var title = wnd.WndCaption.toLowerCase();
    
            // For browsers, title may show full path or filename
            if (title.includes(fileBase) || title.includes(fileName)) {
              Log.Message("PDF file '" + fileName + "' is opened in: " + procName + " - Window: " + wnd.WndCaption);
              found = true;
              break;
            }
          }
    
          if (found) break;
        }
    
        if (found) break;
        Delay(500);
      }
    
      // === Final result
      if (!found) {
        if (!anyViewerFound) {
          Log.Error("No PDF viewer process (application or browser) was started.");
        } else {
          Log.Warning("Viewer is running, but no window was found showing the PDF file: " + fileName);
        }
      }
    }
    

    💬 If a response helped you out, don’t forget to Like it! And if it answered your question, mark it as the solution so others can benefit too.

  • scot1967's avatar
    scot1967
    Icon for Champion Level 2 rankChampion Level 2

    This part is a bit confusing from an automation test perspective,

    I need to test if the PDF has actually successfully opened in the associated PDF viewer. I don't want to assume the user has a specific PDF viewer like Adobe Reader. 

    I am not sure how this could be done unless you check every possible reader.  A developer could build an error check into the application under test but I don't know how you would test this.

    (P.S. Maybe you are spinning up various test environments multiple VMs? )

    • KRogersX's avatar
      KRogersX
      Contributor

      PDF files are typically associated with a reader in Windows (and of course, that association is saved in the Windows Registry). I.e. we don't need to check every possible reader; Windows already knows.

      In .NET, when you create a new process, there's a flag called "UseShellExecute" that tells it to auto-magically find and call the associated application for the specified file (in this case, a PDF file).

      So, like I mentioned, I was hoping for a similar "shortcut" in TestComplete (i.e. "Hey, go find the associated application and tell me it's open."). But since I guess there isn't, as rraghvani​ mentioned, I'll need to manually dig into the registry to find the file association and then check if that associated process is running.

  • In case it's helpful, here's what I came up with. This only works if the associated PDF app is initially opened with the PDF file; if the app is already open, it gets much harder to check. (Chrome and Edge in particular are difficult to check since it's highly likely they are already open.)

    import winreg
    import shlex
    
    class Registry:
    	@staticmethod
    	def ReadValue( root, keyPath, valueName ):
    		try:
    			with winreg.OpenKey( root, keyPath, 0, winreg.KEY_READ ) as key:
    				value, _ = winreg.QueryValueEx( key, valueName )
    				return value
    		except FileNotFoundError:
    			Log.Error( fr"Registry key or value not found: \{keyPath}\{valueName}" )
    		except Exception as e:
    			Log.Error( f"Exception: {e}" )
    
    		return
    
    	@staticmethod
    	def DoesProgIdKeyExist( progId ):
    		try:
    			return winreg.OpenKey( winreg.HKEY_CLASSES_ROOT, progId ) != ""
    		except FileNotFoundError:
    			Log.Error( f"ProgId '{progId}' key not found." )
    		except Exception as e:
    			Log.Error( f"Exception: {e}" )
    
    		return
    
    	@staticmethod
    	def GetAppOpenCommandPath( progIdKey ):
    		try:
    			keyValue = Registry.ReadValue( winreg.HKEY_CLASSES_ROOT, fr"{progIdKey}\shell\open\command", "" )
    			exePath = shlex.split( keyValue )[0]
    			return exePath
    		except FileNotFoundError:
    			Log.Error( f"App open command key not found for key '{progIdKey}'." )
    		except Exception as e:
    			Log.Error( f"Exception: {e}" )
    
    		return
    # end Registry class
    
    def GetProcessName( exePath ):
    	startIndex = exePath.rindex( '\\' ) + 1
    	endIndex = exePath.rindex( '.' )
    	processName = exePath[startIndex:endIndex]
    
    	return processName
    
    def CheckProcessForPdfFile( pdfProcessName, pdfFileName ):
    	result = False
    	processCount = Sys.ChildCount
    	for index in range( 0, processCount - 1 ):
    		process = Sys.Child( index )
    		if pdfProcessName == process.ProcessName:
    			#Log.Message( f"PDF process '{process.ProcessName}' found..." )
    			if pdfFileName.lower() in process.CommandLine.lower():
    				Log.Message( "PDF file in command line.")
    				result = True
    				break
    
    	return result
    
    def Example():
    	pdfExtPath = r"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice"
    	pdfExtValue = "ProgId"
    	progId = Registry.ReadValue(winreg.HKEY_CURRENT_USER, pdfExtPath, pdfExtValue)
    	Log.Message(f"Prog ID = {progId}")
    	
    	exePath = Registry.GetAppOpenCommandPath( progId ) if Registry.DoesProgIdKeyExist( progId ) else ""
    	Log.Message( f"App open command path = {exePath}")
    
    	pdfProcessName = GetProcessName( exePath )
    
    	thePdfFileName = "User-Manual.pdf"
    	if Sys.WaitProcess( pdfProcessName, 5000 ).Exists:
    		Log.Checkpoint( f"PDF viewer '{pdfProcessName}' is open." )
    		if CheckProcessForPdfFile( pdfProcessName, thePdfFileName ):
    			Log.Checkpoint( f"PDF viewer has opened the expected PDF file '{thePdfFileName}'." )
    		else:
    			Log.Warning( f"The expected PDF file '{thePdfFileName}' might be open, but can't determine for sure." ) 
    	else:
    		Log.Error( "PDF viewer is not open." )