def detectDistro(): logger.log("Detecting distribution:", newline=False) if os.environ.get("CERTIFIED_GNOMIE", "no") == "yes": distro = JHBuild() # pragma: no cover elif os.path.exists("/etc/SuSE-release"): distro = Suse() # pragma: no cover elif os.path.exists("/etc/fedora-release"): distro = Fedora() # pragma: no cover elif os.path.exists("/etc/redhat-release"): distro = RHEL() # pragma: no cover elif os.path.exists("/usr/share/doc/ubuntu-minimal"): distro = Ubuntu() elif os.path.exists("/etc/debian_version"): # pragma: no cover distro = Debian() # pragma: no cover elif os.path.exists("/etc/gentoo-release"): # pragma: no cover distro = Gentoo() # pragma: no cover elif os.path.exists("/etc/slackware-version"): # pragma: no cover raise DistributionNotSupportedError("Slackware") # pragma: no cover elif os.path.exists("/var/lib/conarydb/conarydb"): # pragma: no cover distro = Conary() # pragma: no cover elif os.path.exists("/etc/release") and \ re.match(".*Solaris", open("/etc/release").readline()): # pragma: no cover distro = Solaris() # pragma: no cover elif os.path.exists("/etc/os-release") and \ re.match(".*GNOME-Continuous", open("/etc/os-release").readline()): # pragma: no cover distro = GnomeContinuous() # pragma: no cover else: raise DistributionNotSupportedError("Unknown") # pragma: no cover logger.log(distro.__class__.__name__) return distro
def detectDistro(): logger.log("Detecting distribution:", newline=False) if os.environ.get("CERTIFIED_GNOMIE", "no") == "yes": distro = JHBuild() # pragma: no cover elif os.path.exists("/etc/SuSE-release"): distro = Suse() # pragma: no cover elif os.path.exists("/etc/fedora-release"): distro = Fedora() # pragma: no cover elif os.path.exists("/etc/redhat-release"): distro = RHEL() # pragma: no cover elif os.path.exists("/usr/share/doc/ubuntu-minimal"): distro = Ubuntu() elif os.path.exists("/etc/debian_version"): # pragma: no cover distro = Debian() # pragma: no cover elif os.path.exists("/etc/gentoo-release"): # pragma: no cover distro = Gentoo() # pragma: no cover elif os.path.exists("/etc/slackware-version"): # pragma: no cover raise DistributionNotSupportedError("Slackware") # pragma: no cover elif os.path.exists("/var/lib/conarydb/conarydb"): # pragma: no cover distro = Conary() # pragma: no cover elif os.path.exists("/etc/release") and \ re.match(".*Solaris", open("/etc/release").readline()): # pragma: no cover distro = Solaris() # pragma: no cover else: raise DistributionNotSupportedError("Unknown") # pragma: no cover logger.log(distro.__class__.__name__) return distro
def checkForA11yInteractively(): # pragma: no cover """ Checks if accessibility is enabled, and presents a dialog prompting the user if it should be enabled if it is not already, then halts execution. """ if isA11yEnabled(): return from gi.repository import Gtk dialog = Gtk.Dialog('Enable Assistive Technology Support?', None, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, (Gtk.STOCK_QUIT, Gtk.ResponseType.CLOSE, "_Enable", Gtk.ResponseType.ACCEPT)) question = """Dogtail requires that Assistive Technology Support be enabled for it to function. Would you like to enable Assistive Technology support now? Note that you will have to log out for the change to fully take effect. """.strip() dialog.set_default_response(Gtk.ResponseType.ACCEPT) questionLabel = Gtk.Label(label=question) questionLabel.set_line_wrap(True) dialog.vbox.pack_start(questionLabel, True, True, 0) dialog.show_all() result = dialog.run() if result == Gtk.ResponseType.ACCEPT: logger.log("Enabling accessibility...") enableA11y() elif result == Gtk.ResponseType.CLOSE: bailBecauseA11yIsDisabled() dialog.destroy()
def satisfiedByNode(node): # labelled nodes are handled specially: if self.label: # this reverses the search; we're looking for a node with LABELLED_BY # and then checking the label, rather than looking for a label and # then returning whatever LABEL_FOR targets if node.labeller: return stringMatches(self.label, node.labeller.name) else: return False else: # Ensure the node matches any criteria that were set: try: if self.name: if not stringMatches(self.name, node.name): return False if self.roleName: if self.roleName != node.roleName: return False if self.description: if self.description != node.description: return False except GLib.GError as e: if re.match("name :[0-9]+\.[0-9]+ was not provided", e.message): logger.log("Dogtail: warning: omiting possibly broken at-spi application record") return False else: raise e return True
def checkForA11yInteractively(): # pragma: no cover """ Checks if accessibility is enabled, and presents a dialog prompting the user if it should be enabled if it is not already, then halts execution. """ if isA11yEnabled(): return from gi.repository import Gtk dialog = Gtk.Dialog( 'Enable Assistive Technology Support?', None, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, (Gtk.STOCK_QUIT, Gtk.ResponseType.CLOSE, "_Enable", Gtk.ResponseType.ACCEPT)) question = """Dogtail requires that Assistive Technology Support be enabled for it to function. Would you like to enable Assistive Technology support now? Note that you will have to log out for the change to fully take effect. """.strip() dialog.set_default_response(Gtk.ResponseType.ACCEPT) questionLabel = Gtk.Label(label=question) questionLabel.set_line_wrap(True) dialog.vbox.pack_start(questionLabel, True, True, 0) dialog.show_all() result = dialog.run() if result == Gtk.ResponseType.ACCEPT: logger.log("Enabling accessibility...") enableA11y() elif result == Gtk.ResponseType.CLOSE: bailBecauseA11yIsDisabled() dialog.destroy()
def relativeMotion(x, y, mouseDelay=None): logger.log("Mouse relative motion of (%s,%s)" % (x, y)) registry.generateMouseEvent(x, y, 'rel') if mouseDelay: doDelay(mouseDelay) else: doDelay()
def doubleClick(self, button=1): """ Generates a raw mouse double-click event, using the specified button. """ clickX = self.position[0] + self.size[0] / 2 clickY = self.position[1] + self.size[1] / 2 if config.debugSearching: logger.log("raw click on %s %s at (%s,%s)" % (self.name, self.getLogString(), str(clickX), str(clickY))) rawinput.doubleClick(clickX, clickY, button)
def click(x, y, button=1, check=True): """ Synthesize a mouse button click at (x,y) """ if check: checkCoordinates(x, y) logger.log("Mouse button %s click at (%s,%s)" % (button, x, y)) registry.generateMouseEvent(x, y, 'b%sc' % button) doDelay(config.actionDelay)
def release(x, y, button=1, check=True): """ Synthesize a mouse button release at (x,y) """ if check: checkCoordinates(x, y) logger.log("Mouse button %s release at (%s,%s)" % (button, x, y)) registry.generateMouseEvent(x, y, 'b%sr' % button) doDelay()
def doubleClick(x, y, button=1, check=True): """ Synthesize a mouse button double-click at (x,y) """ if check: checkCoordinates(x, y) logger.log("Mouse button %s doubleclick at (%s,%s)" % (button, x, y)) registry.generateMouseEvent(x, y, 'b%sd' % button) doDelay()
def warn(message, caller=True): """ Generate a warning, and pass it to the debug logger. """ frameRec = inspect.stack()[-1] message = "Warning: %s:%s: %s" % (frameRec[1], frameRec[2], message) if caller and frameRec[1] != '<stdin>' and frameRec[1] != '<string>': message = message + ':\n ' + frameRec[4][0] del frameRec logger.log(message)
def doDelay(delay=None): """ Utility function to insert a delay (with logging and a configurable default delay) """ if delay is None: delay = config.defaultDelay if config.debugSleep: logger.log("sleeping for %f" % delay) sleep(delay)
def point(self, mouseDelay=None): """ Move mouse cursor to the center of the widget. """ pointX = self.position[0] + self.size[0] / 2 pointY = self.position[1] + self.size[1] / 2 logger.log("Pointing on %s %s at (%s,%s)" % (self.name, self.getLogString(), str(pointX), str(pointY))) rawinput.registry.generateMouseEvent(pointX, pointY, "abs") if mouseDelay: doDelay(mouseDelay) else: doDelay()
def absoluteMotion(x, y, mouseDelay=None, check=True): """ Synthesize mouse absolute motion to (x,y) """ if check: checkCoordinates(x, y) logger.log("Mouse absolute motion to (%s,%s)" % (x, y)) registry.generateMouseEvent(x, y, 'abs') if mouseDelay: doDelay(mouseDelay) else: doDelay()
def click(self, button=1): """ Generates a raw mouse click event, using the specified button. - 1 is left, - 2 is middle, - 3 is right. """ clickX = self.position[0] + self.size[0] / 2 clickY = self.position[1] + self.size[1] / 2 if config.debugSearching: logger.log("raw click on %s %s at (%s,%s)" % (self.name, self.getLogString(), str(clickX), str(clickY))) rawinput.click(clickX, clickY, button)
def bailBecauseA11yIsDisabled(): if sys.argv[0].endswith("pydoc"): return # pragma: no cover try: if file("/proc/%s/cmdline" % os.getpid()).read().find('epydoc') != -1: return # pragma: no cover except: # pragma: no cover pass # pragma: no cover logger.log("Dogtail requires that Assistive Technology support be enabled." "\nYou can enable accessibility with sniff or by running:\n" "'gsettings set org.gnome.desktop.interface toolkit-accessibility true'\nAborting...") sys.exit(1)
def screenshot(file='screenshot.png', timeStamp=True): """ This function wraps the ImageMagick import command to take a screenshot. The file argument may be specified as 'foo', 'foo.png', or using any other extension that ImageMagick supports. PNG is the default. By default, screenshot filenames are in the format of foo_YYYYMMDD-hhmmss.png . The timeStamp argument may be set to False to name the file foo.png. """ if not isinstance(timeStamp, bool): raise TypeError("timeStampt must be True or False") # config is supposed to create this for us. If it's not there, bail. assert os.path.isdir(config.scratchDir) baseName = ''.join(file.split('.')[0:-1]) fileExt = file.split('.')[-1].lower() if not baseName: baseName = file fileExt = 'png' if timeStamp: ts = TimeStamp() newFile = ts.fileStamp(baseName) + '.' + fileExt path = config.scratchDir + newFile else: newFile = baseName + '.' + fileExt path = config.scratchDir + newFile from gi.repository import Gdk from gi.repository import GObject from gi.repository import GdkPixbuf rootWindow = Gdk.get_default_root_window() geometry = rootWindow.get_geometry() pixbuf = GdkPixbuf.Pixbuf(colorspace=GdkPixbuf.Colorspace.RGB, has_alpha=False, bits_per_sample=8, width=geometry[2], height=geometry[3]) pixbuf = Gdk.pixbuf_get_from_window(rootWindow, 0, 0, geometry[2], geometry[3]) # GdkPixbuf.Pixbuf.save() needs 'jpeg' and not 'jpg' if fileExt == 'jpg': fileExt = 'jpeg' try: pixbuf.savev(path, fileExt, [], []) except GObject.GError: raise ValueError("Failed to save screenshot in %s format" % fileExt) assert os.path.exists(path) logger.log("Screenshot taken: " + path) return path
def screenshot(file='screenshot.png', timeStamp=True): """ This function wraps the ImageMagick import command to take a screenshot. The file argument may be specified as 'foo', 'foo.png', or using any other extension that ImageMagick supports. PNG is the default. By default, screenshot filenames are in the format of foo_YYYYMMDD-hhmmss.png . The timeStamp argument may be set to False to name the file foo.png. """ if not isinstance(timeStamp, bool): raise TypeError("timeStampt must be True or False") # config is supposed to create this for us. If it's not there, bail. assert os.path.isdir(config.scratchDir) baseName = ''.join(file.split('.')[0:-1]) fileExt = file.split('.')[-1].lower() if not baseName: baseName = file fileExt = 'png' if timeStamp: ts = TimeStamp() newFile = ts.fileStamp(baseName) + '.' + fileExt path = config.scratchDir + newFile else: newFile = baseName + '.' + fileExt path = config.scratchDir + newFile from gi.repository import Gdk from gi.repository import GLib from gi.repository import GdkPixbuf rootWindow = Gdk.get_default_root_window() geometry = rootWindow.get_geometry() pixbuf = GdkPixbuf.Pixbuf(colorspace=GdkPixbuf.Colorspace.RGB, has_alpha=False, bits_per_sample=8, width=geometry[2], height=geometry[3]) pixbuf = Gdk.pixbuf_get_from_window(rootWindow, 0, 0, geometry[2], geometry[3]) # GdkPixbuf.Pixbuf.save() needs 'jpeg' and not 'jpg' if fileExt == 'jpg': fileExt = 'jpeg' try: pixbuf.savev(path, fileExt, [], []) except GLib.GError: raise ValueError("Failed to save screenshot in %s format" % fileExt) assert os.path.exists(path) logger.log("Screenshot taken: " + path) return path
def point(self, mouseDelay=None): """ Move mouse cursor to the center of the widget. """ pointX = self.position[0] + self.size[0] / 2 pointY = self.position[1] + self.size[1] / 2 logger.log("Pointing on %s %s at (%s,%s)" % (self.name, self.getLogString(), str(pointX), str(pointY))) rawinput.registry.generateMouseEvent(pointX, pointY, 'abs') if mouseDelay: doDelay(mouseDelay) else: doDelay()
def findChild(self, pred, recursive=True, debugName=None, retry=True, requireResult=True): """ Search for a node satisyfing the predicate, returning a Node. If retry is True (the default), it makes multiple attempts, backing off and retrying on failure, and eventually raises a descriptive exception if the search fails. If retry is False, it gives up after one attempt. If requireResult is True (the default), an exception is raised after all attempts have failed. If it is false, the function simply returns None. """ def describeSearch(parent, pred, recursive, debugName): """ Internal helper function """ if recursive: noun = "descendent" else: noun = "child" if debugName is None: debugName = pred.describeSearchResult() return "%s of %s: %s" % (noun, parent.getLogString(), debugName) assert isinstance(pred, predicate.Predicate) numAttempts = 0 while numAttempts < config.searchCutoffCount: if numAttempts >= config.searchWarningThreshold or config.debugSearching: logger.log("searching for %s (attempt %i)" % (describeSearch(self, pred, recursive, debugName), numAttempts)) result = self._fastFindChild(pred.satisfiedByNode, recursive) if result: assert isinstance(result, Node) if debugName: result.debugName = debugName else: result.debugName = pred.describeSearchResult() return result else: if not retry: break numAttempts += 1 if config.debugSearching or config.debugSleep: logger.log("sleeping for %f" % config.searchBackoffDuration) sleep(config.searchBackoffDuration) if requireResult: raise SearchError(describeSearch(self, pred, recursive, debugName))
def bailBecauseA11yIsDisabled(): if sys.argv[0].endswith("pydoc"): return # pragma: no cover try: if file("/proc/%s/cmdline" % os.getpid()).read().find('epydoc') != -1: return # pragma: no cover except: # pragma: no cover pass # pragma: no cover logger.log( "Dogtail requires that Assistive Technology support be enabled." "\nYou can enable accessibility with sniff or by running:\n" "'gsettings set org.gnome.desktop.interface toolkit-accessibility true'\nAborting..." ) sys.exit(1)
def satisfiedByNode(node): try: return node.roleName == 'application' and stringMatches(self.appName, node.name) except GLib.GError as e: if re.match("name :[0-9]+\.[0-9]+ was not provided", e.message): logger.log("Dogtail: warning: omiting possibly broken at-spi application record") return False else: try: sleep(config.defaults['searchWarningThreshold']) return node.roleName == 'application' and stringMatches(self.appName, node.name) except GLib.GError: logger.log("Dogtail: warning: application may be hanging") return False
def fset(self, text): try: if config.debugSearching: msg = "Setting text of %s to %s" # Let's not get too crazy if 'text' is really large... # FIXME: Sometimes the next line screws up Unicode strings. if len(text) > 140: txt = text[:134] + " [...]" else: txt = text logger.log(msg % (self.getLogString(), "'%s'" % txt)) self.queryEditableText().setTextContents(text) except NotImplementedError: raise AttributeError("can't set attribute")
def load(packageName, language='', getDependencies=True): for moFile in getMoFilesForPackage(packageName, language, getDependencies): # Searching the popt mo-files for translations makes gettext bail out, # so we ignore them here. This is # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 . if not('popt.mo' in moFile or moFile in moFiles): try: translationDbs.append(GettextTranslationDb(moFile)) moFiles[moFile] = None except (AttributeError, IndexError): if config.config.debugTranslation: #import traceback # logger.log(traceback.format_exc()) logger.log( "Warning: Failed to load mo-file for translation: " + moFile)
def drag(fromXY, toXY, button=1, check=True): """ Synthesize a mouse press, drag, and release on the screen. """ logger.log("Mouse button %s drag from %s to %s" % (button, fromXY, toXY)) (x, y) = fromXY press(x, y, button, check) # doDelay() (x, y) = toXY absoluteMotion(x, y, check=check) doDelay() release(x, y, button, check) doDelay()
def clickForward(self): """ Click on the 'Forward' button to advance to next page of wizard. It will log the title of the new page that is reached. FIXME: what if it's Next rather than Forward ??? This will only work if your libgnomeui has accessible buttons; see above. """ fwd = self.child("Forward") fwd.click() # Log the new wizard page; it's helpful when debugging scripts logger.log("%s is now on '%s' page" % (self, self.getPageTitle()))
def do(self): """ Performs the given tree.Action, with appropriate delays and logging. """ logger.log("%s on %s" % (self.name, self.node.getLogString())) if not self.node.sensitive: if config.ensureSensitivity: raise NotSensitiveError(self) else: nSE = NotSensitiveError(self) logger.log("Warning: " + str(nSE)) if config.blinkOnActions: self.node.blink() result = self.__action.doAction(self.__index) doDelay(config.actionDelay) return result
def load(packageName, language='', getDependencies=True): for moFile in getMoFilesForPackage(packageName, language, getDependencies): # Searching the popt mo-files for translations makes gettext bail out, # so we ignore them here. This is # https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=172155 . if not ('popt.mo' in moFile or moFile in moFiles): try: translationDbs.append(GettextTranslationDb(moFile)) moFiles[moFile] = None except (AttributeError, IndexError): if config.config.debugTranslation: #import traceback # logger.log(traceback.format_exc()) logger.log( "Warning: Failed to load mo-file for translation: " + moFile)
def children(self): """a list of this Accessible's children""" if self.parent and self.parent.roleName == 'hyper link': print(self.parent.role) return [] children = [] childCount = self.childCount if childCount > config.childrenLimit: global haveWarnedAboutChildrenLimit if not haveWarnedAboutChildrenLimit: logger.log("Only returning %s children. You may change " "config.childrenLimit if you wish. This message will only" " be printed once." % str(config.childrenLimit)) haveWarnedAboutChildrenLimit = True childCount = config.childrenLimit for i in range(childCount): # Workaround for GNOME bug #465103 # also solution for GNOME bug #321273 try: child = self[i] except LookupError: child = None if child: children.append(child) invalidChildren = childCount - len(children) if invalidChildren and config.debugSearching: logger.log("Skipped %s invalid children of %s" % (invalidChildren, str(self))) try: ht = self.queryHypertext() for li in range(ht.getNLinks()): link = ht.getLink(li) for ai in range(link.nAnchors): child = link.getObject(ai) child.__setupUserData() child.user_data['linkAnchor'] = \ LinkAnchor(node=child, hypertext=ht, linkIndex=li, anchorIndex=ai) children.append(child) except NotImplementedError: pass return children
def translate(srcString): """ Look up srcString in the various translation databases (if any), returning a list of all matches found (potentially the empty list) """ # Use a dict to get uniqueness: results = {} # Try to translate the string: for translationDb in translationDbs: for result in translationDb.getTranslationsOf(srcString): result = safeDecode(result) results[result] = True # No translations found: if len(results) == 0: if config.config.debugTranslation: logger.log('Translation not found for "%s"' % srcString) return results.keys()
def getRelativeSearch(self): """ Get a (ancestorNode, predicate, isRecursive) triple that identifies the best way to find this Node uniquely. FIXME: or None if no such search exists? FIXME: may need to make this more robust FIXME: should this be private? """ if config.debugSearchPaths: logger.log("getRelativeSearchPath(%s)" % self) assert self assert self.parent isRecursive = False ancestor = self.parent # iterate up ancestors until you reach an identifiable one, # setting the search to be isRecursive if need be: while not self.__nodeIsIdentifiable(ancestor): ancestor = ancestor.parent isRecursive = True # Pick the most appropriate predicate for finding this node: if self.labellee: if self.labellee.name: return (ancestor, predicate.IsLabelledAs(self.labellee.name), isRecursive) if self.roleName == 'menu': return (ancestor, predicate.IsAMenuNamed(self.name), isRecursive) elif self.roleName == 'menu item' or self.roleName == 'check menu item': return (ancestor, predicate.IsAMenuItemNamed(self.name), isRecursive) elif self.roleName == 'text': return (ancestor, predicate.IsATextEntryNamed(self.name), isRecursive) elif self.roleName == 'push button': return (ancestor, predicate.IsAButtonNamed(self.name), isRecursive) elif self.roleName == 'frame': return (ancestor, predicate.IsAWindowNamed(self.name), isRecursive) elif self.roleName == 'dialog': return (ancestor, predicate.IsADialogNamed(self.name), isRecursive) else: pred = predicate.GenericPredicate( name=self.name, roleName=self.roleName) return (ancestor, pred, isRecursive)
def getAbsoluteSearchPath(self): """ FIXME: this needs rewriting... Generate a SearchPath instance giving the 'best' way to find the Accessible wrapped by this node again, starting at the root and applying each search in turn. This is somewhat analagous to an absolute path in a filesystem, except that some of searches may be recursive, rather than just searching direct children. Used by the recording framework for identifying nodes in a persistent way, independent of the style of script being written. FIXME: try to ensure uniqueness FIXME: need some heuristics to get 'good' searches, whatever that means """ if config.debugSearchPaths: logger.log("getAbsoluteSearchPath(%s)" % self) if self.roleName == 'application': result = path.SearchPath() result.append(predicate.IsAnApplicationNamed(self.name), False) return result else: if self.parent: (ancestor, pred, isRecursive) = self.getRelativeSearch() if config.debugSearchPaths: logger.log("got ancestor: %s" % ancestor) ancestorPath = ancestor.getAbsoluteSearchPath() ancestorPath.append(pred, isRecursive) return ancestorPath else: # This should be the root node: return path.SearchPath()
def keyCombo(self, comboString): if config.debugSearching: logger.log("Pressing keys '%s' into %s" % (comboString, self.getLogString())) if self.focusable: if not self.focused: try: self.grabFocus() except Exception: logger.log("Node is focusable but I can't grabFocus!") else: logger.log("Node is not focusable; trying key combo anyway") rawinput.keyCombo(comboString)
def typeText(self, string): """ Type the given text into the node, with appropriate delays and logging. """ logger.log("Typing text into %s: '%s'" % (self.getLogString(), string)) if self.focusable: if not self.focused: try: self.grabFocus() except Exception: logger.log("Node is focusable but I can't grabFocus!") rawinput.typeText(string) else: logger.log("Node is not focusable; falling back to inserting text") et = self.queryEditableText() et.insertText(self.caretOffset, string, len(string)) self.caretOffset += len(string) doDelay()
def fset(self, value): logger.log("Setting combobox %s to '%s'" % (self.getLogString(), value)) self.childNamed(childName=value).doActionNamed('click') doDelay()
see above. """ fwd = self.child("Apply") fwd.click() # FIXME: debug logging? Accessibility.Accessible.__bases__ = ( Application, Root, Node,) + Accessibility.Accessible.__bases__ try: root = pyatspi.Registry.getDesktop(0) root.debugName = 'root' except Exception: # pragma: no cover # Warn if AT-SPI's desktop object doesn't show up. logger.log( "Error: AT-SPI's desktop is not visible. Do you have accessibility enabled?") # Check that there are applications running. Warn if none are. children = root.children if not children: # pragma: no cover logger.log( "Warning: AT-SPI's desktop is visible but it has no children. Are you running any AT-SPI-aware applications?") del children import os # sniff also imports from tree and we don't want to run this code from # sniff itself if not os.path.exists('/tmp/sniff_running.lock'): if not os.path.exists('/tmp/sniff_refresh.lock'): # may have already been locked by dogtail.procedural # tell sniff not to use auto-refresh while script using this module is # running
def __init__(self, node, debugName=None): Node.__init__(self, node) if debugName: self.debugName = debugName logger.log("%s is on '%s' page" % (self, self.getPageTitle()))